springmvc

SpringMVC

目录

1.什么是MVC?

  • 模型(Dao,Service),视图(JSP),控制器(Servlet)
  • 是一种代码规范

职责分析:

Controller:控制器servelt

  1. 取得表单数据
  2. 调用业务逻辑
  3. 转向指定的页面

Model:模型,指工程中的javaBean,用来处理数据

  1. 一类是实体类(pojo)Bean,专门用来储存数据
  2. 一类是业务Bean,指Service和Dao对象,专门用来处理业务逻辑和数据访问的

View:视图

  1. 显示页面

2.什么是SpringMVC?

2.1丶概念

Spring MVC是Spring的后续产品,是基于Java实现MVC的轻量级Web框架。

2.2丶优点

  1. 轻量级,简单易学
  2. 高效 , 基于请求响应的MVC框架
  3. 与Spring兼容性好(导的包跟spring很多一样),无缝结合
  4. 基于sevlet,通过功能强大的DispatherServlet
  5. 约定优于配置
  6. 功能强大:RESTful、数据验证、格式化、本地化、主题等
  7. 简洁灵活

3.Helloword

1.配置

依赖和添加web模块

  • 创建项目,导入依赖

    <dependencies>
    <!-- SpringMVC -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.1</version>
    </dependency>
    <!-- 日志 -->
    <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
    </dependency>
    <!-- ServletAPI -->
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
    </dependency>
    <!-- Spring5和Thymeleaf整合包 -->
    <dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.12.RELEASE</version>
    </dependency>
    </dependencies>
    
  • scr 目录下 main目录下创建一个webapp目录

    1647138695660

  • 项目结构里模块中,添加web.xml到webapp下

    1647224022220

1647138992880

xml配置(注册DispatcherServlet)

  • 我们前面知道,DispatcherServlet前端控制器是基于Servelt的,所以也需要像Servelt一样注册

  • 可通过init-param标签设置SpringMVC配置文件的位置和名称,通过load-on-startup标签设置 SpringMVC前端控制器DispatcherServlet的初始化时间

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

    <!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
    <servlet>
        <servlet-name>SpringMvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 通过初始化参数指定SpringMVC配置文件的位置和名称 -->
    <init-param>
        <!-- contextConfigLocation为固定值 -->
        <param-name>contextConfigLocation</param-name>
        <!-- 使用classpath:表示从类路径(main下的其他目录)查找配置文件,例如maven工程中的
src/main/resources -->
        <param-value>classpath:SpringMvc.xml</param-value>
    </init-param>
        <!--
作为框架的核心组件,在启动过程中有大量的初始化操作要做
而这些操作放在第一次请求时才执行会严重影响访问速度
因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时
-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!--
设置springMVC的核心控制器所能处理的请求的请求路径
/所匹配的请求可以是/login或.html或.js或.css方式的请求路径
但是/不能匹配.jsp请求路径的请求,因为jsp就是servelt,所以需要特殊的servlet来匹配
/*可以匹配到jsp-->
</web-app>

2.创建请求控制器

由于前端控制器对浏览器发送的请求进行了统一的处理,但是具体的请求有不同的处理过程,因此需要 创建处理具体请求的类,即请求控制器 请求控制器中每一个处理请求的方法成为控制器方法 因为SpringMVC的控制器由一个POJO(普通的Java类)担任,因此需要通过@Controller注解将其标识 为一个控制层组件,交给Spring的IoC容器管理,此时SpringMVC才能够识别控制器的存在

@Controller
public class HelloWorld {
}

3.创建SpringMvc的配置文件

<?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.LiuDeHen.Controller"/>

   <!-- 配置Thymeleaf视图解析器 -->
   <bean id="viewResolver"
         class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
   <property name="order" value="1"/>
   <property name="characterEncoding" value="UTF-8"/>
   <property name="templateEngine">
   <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
      <property name="templateResolver">
         <bean
                 class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
            <!-- 视图前缀,所以我们要在WEB-Inf下创建templates目录 -->
            <property name="prefix" value="/WEB-INF/templates/"/>
            <!-- 视图后缀 -->
            <property name="suffix" value=".html"/>
            <property name="templateMode" value="HTML5"/>
            <property name="characterEncoding" value="UTF-8" />
         </bean>
      </property>
   </bean>
   </property>
   </bean>
</beans>

4.启动测试

注意:在启动的时候,出现异常,查看tocat日志,如果是没找到什么什么包之类的就是war打包没有把lib包打进去,解决:进入项目管理,点开工件,找到项目,查看web-INF下有没有lib包,没有就加进去,并把所有库文件加入

a>实现对首页的访问

// @RequestMapping注解:处理请求和控制器方法之间的映射关系
// @RequestMapping注解的value属性可以通过请求地址匹配请求,/表示的当前工程的上下文路径
@RequestMapping("/")
public String index() {
//设置视图名称
return "index";
}

b>通过超链接跳转到指定页面

  • a标签下的/是绝对路径
    • 绝对路径又分为被浏览器解析和服务器解析
    • 而超链接就是浏览器解析,所以这里的/是localhost:8080缺少一个Springmvc(上下文路径)
    • 这里用thymeleaf的语法,会根据我们的绝对路径自动帮我们加上上下文路径
<!DOCTYPE html>
<html lang="en"xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<a th:href="@{/target}">跳转</a>
</body>
</html>

5.总结

浏览器发送请求到前端控制器的ul-pattern,该请求就会被DispatcherServlet处理,前端控制器会读取springmvc的核心配置文件,通过组件扫描找到控制器,将请求地址和控制器中有@RequestMapping注解的方法的value属性值进行匹配,如果成功就会返回一个字符串类型的视图名称,这个视图名称会被视图解析器解析,加上前后缀,再通过 通过Thymeleaf对视图进行渲染,最终转发到视图所对应页

4. @RequestMapping注解

1.当有多个方法对应一个方法时就会出现异常

@RequestMapping("/")
    public String index(){
        return "index";
    }
@RequestMapping("/")
    public String index2(){
        return "index2";
    }

1647148533976

2.@RequestMapping注解的功能

从注解名称上我们可以看到,@RequestMapping注解的作用就是将请求和处理请求的控制器方法关联 起来,建立映射关系。 SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的控制器方法来处理这个请求。

3.@RequestMapping注解的位置

  • @Target({ElementType.TYPE, ElementType.METHOD})
    
  • @RequestMapping标识一个:设置映射请求的请求路径的初始信息

  • @RequestMapping标识一个方法:设置映射请求请求路径的具体信息

4.@RequestMapping注解的value属性

  • @AliasFor("path")
        String[] value() default {};
    
  • @RequestMapping注解的value属性通过请求的请求地址匹配请求映射

  • @RequestMapping注解的value属性是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址 所对应的请求

  • @RequestMapping注解的value属性必须设置,至少通过请求地址匹配请求映射

  @RequestMapping(value={"/","/target"})
    public String index(){
        return "index";
    }

5.@RequestMapping注解的method属性

  • @RequestMapping注解的method属性通过请求的请求方式(get或post)匹配请求映射
  • 聊聊get和post请求
    • get速度快不安全,传的数据少,因为参数属性跟着url传过去
    • post速度慢些安全,传的数据多,因为参数属性在请求体里(所以只有post请求才有请求体)
    • 上传文件时,不能用get,因为把文件拼接到url的时候,只是把文件名拼上去了,所以只能用post请求
    • 超链接是get请求
  • @RequestMapping注解的method属性是一个RequestMethod类型的数组,表示该请求映射能够匹配 多种请求方式的请求
  • 若当前请求的请求地址满足请求映射的value属性,但是请求方式不满足method属性,则浏览器报错 405:Request method 'POST' not supported
  • 实例:
<form th:action="@{/target}" method="post">
    <input type="submit">post
</form>
<form th:action="@{/target}" method="get">
    <input type="submit">get
</form>
@RequestMapping(value="/target"
 // RequestMethod这个类里定义了所有请求   
    ,method = {RequestMethod.GET}
    )
  • 此时我们定义了get请求,就只能通过get请求,如果我们没有定义method属性,那么任何请求都可以
  • 注: 1、对于处理指定请求方式的控制器方法,SpringMVC中提供了@RequestMapping的派生注解
  • 处理get请求的映射-->@GetMapping
  • 处理post请求的映射-->@PostMapping
  • 处理put请求的映射-->@PutMapping
  • 处理delete请求的映射-->@DeleteMapping
  • 2、常用的请求方式有get,post,put,delete
    - 浏览器只支持get和post请求
  • 可以使用这些注解代理method属性

6.@RequestMapping注解的params属性(了解)

  • @RequestMapping注解的params属性通过请求的请求参数匹配请求映射

  • @RequestMapping注解的params属性是一个字符串类型的数组,可以通过四种表达式设置请求参数 和请求映射的匹配关系

  • "param":要求请求映射所匹配的请求必须携带param请求参数

  • "!param":要求请求映射所匹配的请求必须不能携带param请求参数

  • "param=value":要求请求映射所匹配的请求必须携带param请求参数且param=value

  • "param!=value":要求请求映射所匹配的请求必须携带param请求参数但是param!=value

    <a th:href="@{/test(username='admin',password=123456)">测试@RequestMapping的
    params属性-->/test</a><br>
    
    @RequestMapping(
    value = {"/testRequestMapping", "/test"}
    ,method = {RequestMethod.GET, RequestMethod.POST}
    ,params = {"username","password!=123456"}
    )
    public String testRequestMapping(){
    return "success";
    }
    

注: 若当前请求满足@RequestMapping注解的value和method属性,但是不满足params属性,此时 页面回报错400:Parameter conditions "username, password!=123456" not met for actual request parameters: username={admin}, password=

7.@RequestMapping注解的headers属性(了解)

  1. @RequestMapping注解的headers属性通过请求的请求头信息匹配请求映射

  2. @RequestMapping注解的headers属性是一个字符串类型的数组,可以通过四种表达式设置请求头信 息和请求映射的匹配关系

    1. "header":要求请求映射所匹配的请求必须携带header请求头信息

    2. "!header":要求请求映射所匹配的请求必须不能携带header请求头信息

    3. "header=value":要求请求映射所匹配的请求必须携带header请求头信息且header=value

    4. "header!=value":要求请求映射所匹配的请求必须携带header请求头信息且header!=value

注:若当前请求满足@RequestMapping注解的value和method属性,但是不满足headers属性,此时页面 显示404错误,即资源未找到

8.SpringMVC支持ant风格的路径

?:表示任意的单个字符

*:表示任意的0个或多个字符

**:表示任意的一层或多层目录

注意:在使用时,只能使用//xxx的方式

<a th:href="@{/a1b}">ant风格--?</a> <br>
<a th:href="@{/12345a/ab}">ant风格--*</a> <br>
<a th:href="@{/1/2/3/4/ab}">ant风格--**</a>
//    @RequestMapping("/a?b")
//@RequestMapping("/*a/ab")
    @RequestMapping("/**/ab")
    public String  ant(){
        return "index2";
    }

9.SpringMVC支持路径中的占位符(重点)

  1. 原始方式:/deleteUser?id=1

  2. rest方式:/deleteUser/1

  3. SpringMVC路径中的占位符常用于RESTful风格中,当请求路径中将某些数据通过路径的方式传输到服 务器中,就可以在相应的@RequestMapping注解的value属性中通过占位符{xxx}表示传输的数据,在 通过@PathVariable注解,将占位符所表示的数据赋值给控制器方法的形参

<a th:href="@{/rest/123/liuchao}">rest风格</a>
  @RequestMapping("/rest/{id}/{name}")
    public String rest(@PathVariable("id")int id,@PathVariable("name") String name){
        System.out.println("name:"+name);
        System.out.println("id:"+id);
        return "index2";
    }

5.SpringMVC获取请求参数

1、通过ServletAPI获取

通过ServletAPI获取 将HttpServletRequest作为控制器方法的形参,此时HttpServletRequest类型的参数表示封装了来自前端控制器里请求的请求报文的对象

@RequestMapping("/testParam")
public String testParam(HttpServletRequest request){
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username:"+username+",password:"+password);
return "success";
}

2、通过控制器方法的形参获取请求参数

在控制器方法的形参位置,设置和请求参数同名的形参,当浏览器发送请求,匹配到请求映射时,DispatcherServlet就会将请求参数赋值给相应的形参

<a th:href="@{/request(username='liuchao',pwd='123')}">形参获取请求</a>

//多个名字相同的参数
<form th:action="@{/request2}" method="get">
    <input type="checkbox" name="hobby" value="java"> java <br>
    <input type="checkbox" name="hobby" value="python"> python <br>
    <input type="checkbox" name="hobby" value="c"> c <br>
    <input type="submit" value="请求2">
</form>
  @RequestMapping("/request")
    public String re(String name,String pwd){
        System.out.println("name:"+name);
        System.out.println("pwd:"+pwd);
        return "index2";
    }
}
//多个名字相同的参数,可以使用字符串,会打出一个  java,python,c的字符串
//    也可以使用数组,遍历一下
  @RequestMapping("/request2")
    public String re2(String hobby/String []hobby){
        System.out.println(hobby);
        return "index2";
    }

注: 若请求所传输的请求参数中有多个同名的请求参数,此时可以在控制器方法的形参中设置字符串 数组或者字符串类型的形参接收此请求参数 若使用字符串数组类型的形参,此参数的数组中包含了每一个数据 若使用字符串类型的形参,此参数的值为每个数据中间使用逗号拼接的结果

3、@RequestParam

  • @RequestParam是将请求参数和控制器方法的形参创建映射关系

  • @RequestParam注解一共有三个属性:

  • value:指定为形参赋值的请求参数的参数名

  • required:设置是否必须传输此请求参数,默认值为true

  • defaultValue:不管required属性值为true或false,当value所指定的请求参数没有传输或传输的值 为""时则使用默认值为形参赋值

  • //默认为true也就是当没有这个参数且没有defaultValue的时候就报错
    boolean required() default true;
    
  @RequestMapping("/RequestParam")
    public String re3(@RequestParam(value = "User_name",required = false,defaultValue="默认值")
                                  String username){
        System.out.println(username);
        return "index2";
    }

4、@RequestHeader

@RequestHeader是将请求头信息和控制器方法的形参创建映射关系

@RequestHeader注解一共有三个属性:value、required、defaultValue,用法同@RequestParam

 @RequestMapping("/RequestParam2")
//这里value如果填一个请求头里没有的键,就会输出默认值haha
    public String re4(@RequestHeader(value = "Cookie",required = false,defaultValue ="haha")
                                  String cookie){
        System.out.println(cookie);
        return "index2";
    }

5、@CookieValue

  • session和cookie
    • session存储在服务器
    • cookie在浏览器
    • 当第一个创建一个session,session会在响应报文中由Cookie保存,如果是在请求报文中就不是第一次创建这个session
      • 流程:创建第一次的session
      • 我们当前的httpsession对象存储到我们当前的服务器维护的map集合中,以Cookie的随机序列作为map集合的键,然后把session对象作为map集合的值,这个时候再把cookie响应到浏览器
    • 第一次之后,Cookie就存在请求报文中了(因为Cookie存在在浏览器之后,后面的每一次请求都会携带这个Cookie)

@CookieValue是将cookie数据和控制器方法的形参创建映射关系 @CookieValue注解一共有三个属性:value、required、defaultValue,用法同@RequestParam

	//这个JSESSIONID是cookie的键名,在第一次之后的请求头中可以找到
@RequestMapping("/RequestParam3")
    public String re5(@RequestHeader(value = "Cookie",required = false,defaultValue ="haha")								
                              String cookie,@CookieValue("JSESSIONID")String JSESSIONID){
        System.out.println(JSESSIONID);
        return "index2";
    }

6、通过POJO获取请求参数

可以在控制器方法的形参位置设置一个实体类类型的形参,此时若浏览器传输的请求参数的参数名和实 体类中的属性名一致,那么请求参数就会为此属性赋值

//实体类	
private String name;
    private String pwd;
    private String sex;
    private int age;
//控制层
@RequestMapping("/POJO")
    public String re6(User user){
        System.out.println(user);
        return "index2";
    }
//前端表单
<form th:action="@{/POJO}" method="get">
    用户:<input type="text" name="username"> <br>
    密码:<input type="password" name="pwd"> <br>
    性别: <input type="radio" name="sex" value="男">
         <input type="radio" name="sex" value="女"> <br>
    <input type="submit">
</form>
  • 我们实体类中比表单多一个属性,那么这个属性就没有被赋上值,为0

7丶乱码问题解决

  • get请求乱码:get请求数据是跟随url传输,所以在tomcat的server.xml文件上加上urlEncoding=utf-8就行,tomcat8以上默认utf-8
  • post请求乱码:
    • 必须在我们前端控制器创建之前,也就是servlet生成之前设置编码
    • 过滤器在servlet之前生成
    • 配置在web.xml的过滤器
    <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>
        <init-param>
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
  • 过滤器源码分析

    • 点开CharacterEncodingFilter类,看到三个参数

      	//envoding可以为空
      	@Nullable
          private String encoding;
          private boolean forceRequestEncoding;
          private boolean forceResponseEncoding;
      
    • 设置encoding为UTF-8

    • 找到方法doFilterInternal,因为它有参数FilterChain filterChain

          protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
              String encoding = this.getEncoding();
             //我们上面设置了encoding
              if (encoding != null) {
                  //isForceRequestEncoding的参数就是forceRequestEncoding构造器默认给了false,而request.getCharacterEncoding() == null成立为true,所以设置了请求过滤器
                  if (this.isForceRequestEncoding() || request.getCharacterEncoding() == null) {
                      request.setCharacterEncoding(encoding);
                  }
      //this.isForceResponseEncoding()默认为false,所以我们要设置				forceResponseEncoding为true开启响应过滤器
                  if (this.isForceResponseEncoding()) {
                      response.setCharacterEncoding(encoding);
                  }
              }
      
              filterChain.doFilter(request, response);
          }
      

6.域对象共享数据

1丶有哪些域对象

  • request请求域: 在service 方法调用前由服务器创建,传入service方法。整个请求结束,request生命结束
  • application域(ServletContext):服务器开启到关闭
  • session:浏览器会话开启到关闭
    • 钝化和活化
    • 钝化:当服务器关闭,浏览器没有关,就会将session序列化到硬盘
    • 活化:当浏览器再次开启,就会反序列化重写读取session
  • pagecontext:jsp页面

2丶使用serveltApi获取request域

请求域的范围就是一整个请求的过程中,所以转发到success页面同样,能共享这个请求对象

  @RequestMapping("/api")
    public String api(HttpServletRequest request){
        request.setAttribute("user","hello,api");
        return "success";
    }
<p th:text="${user}"></p>

3、使用ModelAndView向request域对象共享数据(重)

  • 必须作为返回值

  • 无论是使用原始api还是model,map,modelMap底层都是会将数据封装成ModelAndView类型

    1647233065379

  • model用来向请求域共享数据

  • view用来设置视图名称,完成跳转

 @RequestMapping("/modelAndView")
    public ModelAndView modelAndView(){
        ModelAndView mav = new ModelAndView();
//        模型和视图
//        model用来向请求域共享数据
        mav.addObject("user","hello,modelAndView");
//        view用来设置视图名称,完成跳转
        mav.setViewName("success");
        return mav;
    }

4、使用Model向request域对象共享数据

 @RequestMapping("/model")
    public String model(Model model){
       model.addAttribute("user","hello,model");
       return "success";
    }

6、使用map向request域对象共享数据

   @RequestMapping("/map")
    public String map(Map map){
        map.put("user","hello,map");
        return "success";
    }

7、使用ModelMap向request域对象共享数据

@RequestMapping("/ModelMap")
    public String ModelMap(ModelMap map){
//        map.put("user","hello,map");
//        addAttribute底层也就是封装了put方法
        map.addAttribute("user","hello,ModelMap");
        return "success";
    }

8 、Model、ModelMap、Map的关系

  • 通过反射我们看到他们的运行时的类都是org.springframework.validation.support.BindingAwareModelMap

     System.out.println(model.getClass().getName());
     System.out.println(map.getClass().getName());
     System.out.println(modelMap.getClass().getName());
    
  • 看看他们的类关系

    public interface Model{}
    public class ModelMap extends LinkedHashMap<String, Object> {}
    public class ExtendedModelMap extends ModelMap implements Model {}
    public class BindingAwareModelMap extends ExtendedModelMap {}
    

9、向session域共享数据

  • 作用时间:浏览器开启到关闭

    @RequestMapping("/session")
        public String session(HttpSession session){
            session.setAttribute("sessionCode","hello,session");
            return "success";
        }
    
    <p th:text="${session.sessionCode}"></p>
    

10、向application域共享数据

   @RequestMapping("/application")
    public String application(HttpSession session,HttpServletRequest request){
//        1.用session获取
        ServletContext application = session.getServletContext();
        application.setAttribute("user1","hello,application");
//       2.用request获取session再获取
        ServletContext application2 = request.getSession().getServletContext();
        application2.setAttribute("user2","hello,application2");
//        3.发现两个类是一样的,application是全局唯一只有一个的
        System.out.println(application.getClass().getName());
        System.out.println(application2.getClass().getName());
        return "success";
    }

7.SpringMvc视图

SpringMVC中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户 SpringMVC视图的种类很多,默认有转发视图和重定向视图 当工程引入jstl的依赖,转发视图会自动转换为JstlView 若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视 图解析器解析之后所得到的是ThymeleafView

  • 没有前缀才会去走我们配置的视图解析器

  • 调试进入现在调试器找到这个方法就会进入前端控制器那个类

1、ThymeleafView

当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中所配置 的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转

1647243657269

  • 找到processDispatchResult方法打断点,再点进去

    1647243820550

  • 找到render方法(执行,渲染),点进去,看到下面源码

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        //获取地区
            Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();
            response.setLocale(locale);
        //获取我们设置的视图名称
            String viewName = mv.getViewName();
            View view;
        //如果视图名称不为空就
            if (viewName != null) {
                //解析我们的视图名称,mv.getModelInternal()点进去就看到是获取的model模型
                view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
    

    1647245053479

2、转发视图

SpringMVC中默认的转发视图是InternalResourceView

SpringMVC中创建转发视图的情况: 当控制器方法中所设置的视图名称以"forward:"为前缀时创建InternalResourceView视图,此时的视 图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"forward:"去掉,剩余部分作为最终路径通过转发的方式实现跳转

   @RequestMapping("/forward")
    public String forward(){
//用来跳转到其他请求上
        return "forward:/view";
    }
  • 前面跳转过程跟上面一样,最后都要到render这个渲染方法里

1647245964436

3、重定向视图

  • 重定向和转发的区别(重点

    • 重定向:
      • 浏览器发送两次请求,第一次到servlet,第二次到重定向的地址
      • 地址是第二次重定向的地址
      • 不能使用请求域,因为第二次是第二个请求,不是同一个请求域不能共享
      • 不能访问WEB-INF下的资源(因为WEB-INF下的资源具有安全性,只能通过服务器内部访问,不能通过浏览器访问)
      • 可以跨域(浏览器发送两次请求,可以访问外部资源,比如重定向可以访问百度,转发不行)
      • 当我们登录的时候,或者跳转重新到一个页面的时候,我们用重定向,因为这个时候地址会改变
    • 转发:
      • 浏览器发送一次请求(发生在服务器内部)
      • 地址就是第一次发送请求的地址
      • 可以使用请求域
      • 能访问WEB-INF下的资源
      • 不能跨域(转发发生在服务器内部,只能访问服务器内部资源)
  • SpringMVC中默认的重定向视图是RedirectView 当控制器方法中所设置的视图名称以"redirect:"为前缀时,创建RedirectView视图,此时的视图名称不 会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"redirect:"去掉,剩余部分作为最 终路径通过重定向的方式实现跳转 例如"redirect:/","redirect:/employee"

@RequestMapping("/redirect")
    public String redirect(){
        return "redirect:/view";
    }

1647247903217

4、视图控制器view-controller

控制器方法中,仅仅用来实现页面跳转(不处理数据),即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示

path:设置处理的请求地址
view-name:设置请求地址所对应的视图名称
<mvc:view-controller path="/" view-name="index"/>
    //这里index可能爆红,但是运行是没有问题的

注: 当SpringMVC中设置任何一个view-controller时其他控制器中的请求映射将全部失效,此时需 要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签:

<mvc:annotation-driven />

5丶InternalResourceViewResolver

也就是使用jsp时的视图解析器配置

这个时候默认“index”=forward:“/index”

<bean  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/templates/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

8.RESTful

1、RESTful简介

REST:Representational State Transfer,表现层资源状态转移。

a>资源

​ 资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个 可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、 数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端 应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个 资源可以由一个或多个URI来标识。URI既是资源的名称,也是资源在Web上的地址。对某个资源感兴 趣的客户端应用,可以通过资源的URI与其进行交互。

b>资源的表述

​ 资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交 换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格 式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。

c>状态转移

​ 状态转移说的是:在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资 源的表述,来间接实现操作资源的目的。

2、RESTful的实现

具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。

它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。

REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开不使用问号键值对方 式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。

操作 传统方式 REST风格
查询操作 getUserById?id=1 user/1-->get请求方式
保存操作 saveUser user-->post请求方式
删除操作 deleteUser?id=1 user/1-->delete请求方式
更新操作 updateUser user-->put请求方式

3、HiddenHttpMethodFilter(请求过滤器)

由于浏览器只支持发送get和post方式的请求,那么该如何发送put和delete请求呢?(ajax其实也可以,但是ajax的这两请求有部分浏览器不支持)

SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们将 POST 请求转换为 DELETE 或 PUT 请求 HiddenHttpMethodFilter 处理put和delete请求的条件:

a>当前请求的请求方式必须为post

b>当前请求必须传输请求参数_method _

_满足以上条件,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数 _method的值,因此请求参数_method的值才是最终的请求方式 在web.xml中注册HiddenHttpMethodFilter

<filter>
	<filter-name>HiddenHttpMethodFilter</filter-name>
	<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filterclass>
</filter>
<filter-mapping>
	<filter-name>HiddenHttpMethodFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

源码分析

  • 点进HiddenHttpMethodFilter

  • 找到doFilterInternal方法,因为这个方法有FilterChain参数,可以为过滤器放行

        private static final List<String> ALLOWED_METHODS;
        public static final String DEFAULT_METHOD_PARAM = "_method";
        private String methodParam = "_method";
    
    
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //1.首先将我们的请求用自己定义的存储起来    
        HttpServletRequest requestToUse = request;
        //2.判断当前请求是否为POst,后面那个判断为null恒成立
            if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
        //3.根据methodParam的“_method”定义我们自己请求的参数
                String paramValue = request.getParameter(this.methodParam);
        //4.此时我们的请求参数不为空进入        
                if (StringUtils.hasLength(paramValue)) {
        //5.将我们的请求参数变为大写            
                    String method = paramValue.toUpperCase(Locale.ENGLISH);
        //6.在ALLOWED_METHODS里看有没有当前请求      
       	//     ALLOWED_METHODS在下面静态代码块定义了,有put,delete,PATCH        
                    if (ALLOWED_METHODS.contains(method)) {
        //7.点进 HttpMethodRequestWrapper 方法              
                        requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                    }
                }
            }
    	//10.根据新的请求设置放行
            filterChain.doFilter((ServletRequest)requestToUse, response);
        }
    
        static {
            ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
        }
    
     private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
            private final String method;
    
            public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
            //8.根据请求和方法得到一个新的请求
                super(request);
                this.method = method;
            }
    		//9.并返回一个新的方法,也就是我们后面都用的这个方法
            public String getMethod() {
                return this.method;
            }
        }
    
  • 具体操作

    • 首先配置好web.xml

    • 前端,使用hideen隐藏域,定义好_method,值为请求的方式

      <a th:href="@{/user}">查询所有用户</a> <br>
      <a th:href="@{/userID/1}">根据id查询用户</a> <br>
      post:
      <form th:action="@{/userpost}" method="post">
          <input type="text" name="username"><br>
          <input type="password" name="password"><br>
          <input type="submit" value="添加">
      </form>
      <br>
      put:
      <form th:action="@{/userput}" method="post">
          <input type="hidden" name="_method" value="put">
          <input type="text" name="username"><br>
          <input type="password" name="password"><br>
          <input type="submit" value="添加">
      </form>
      
    • controller

      • 查询时使用url直接接收数据时,记得要用占位符
      • 要根据是增删查改,来定义请求的方式
        @RequestMapping("/user")
          public String user(){
              System.out.println("查询所有用户");
              return "success";
          }
      
          @RequestMapping(value = "/userID/{id}",method = RequestMethod.GET)
          public String userID(@PathVariable("id") String id){
              System.out.println("id:"+id);
              System.out.println("根据有id查询用户");
              return "success";
          }
      
          @RequestMapping(value = "/userpost",method = RequestMethod.POST)
          public String userpost(String username,String password){
              System.out.println("username:"+username);
              System.out.println("password:"+password);
              System.out.println("保存用户信息");
              return "success";
          }
      
          @RequestMapping(value = "/userput",method = RequestMethod.PUT)
          public String userput(String username,String password){
              System.out.println("username:"+username);
              System.out.println("password:"+password);
              System.out.println("添加用户信息");
              return "success";
          }
      

    注:如果出现乱码,可能是因为,你请求过滤器(获取了_method的请求参数),在编码过滤器前,编码过滤器只要之前有之前过滤器获取过请求参数就失效了

    解决:改变顺序就可以了

9.RESTful案例

1、准备工作

和传统 CRUD 一样,实现对员工信息的增删改查。

  • 搭建环境
  • 准备实体类
public class Employee {
private Integer id;
private String lastName;
private String email;
//1 male, 0 female
private Integer gender;
//get,set方法
//有参无参构造器
}
  • 准备dao模拟数据 库
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees = null;
static{
employees = new HashMap<Integer, Employee>();
employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1));
employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1));
employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0));
employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0));
employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1));
}
private static Integer initId = 1006;
public void save(Employee employee){
if(employee.getId() == null){
employee.setId(initId++);
}
employees.put(employee.getId(), employee);
}
public Collection<Employee> getAll(){
return employees.values();
}
public Employee get(Integer id){
return employees.get(id);
}
public void delete(Integer id){
employees.remove(id);
}
}

2、功能清单

1647330000673

3、具体功能:访问首页

a>配置view-controller

<mvc:view-controller path="/" view-name="index"/>

b>创建页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" >
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<a th:href="@{/employee}">访问员工信息</a>
</body>
</html>

4、具体功能:查询所有员工数据

a>控制器方法

@RequestMapping(value = "/employee", method = RequestMethod.GET)
public String getEmployeeList(Model model){
Collection<Employee> employeeList = employeeDao.getAll();
model.addAttribute("employeeList", employeeList);
return "employee_list";
}

b>创建employee_list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Employee Info</title>
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
</head>
    <body>
<table border="1" cellpadding="0" cellspacing="0" style="text-align:
center;" id="dataTable">
<tr>
<th colspan="5">Employee Info</th>
</tr>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>options(<a th:href="@{/toAdd}">add</a>)</th>
</tr>
<tr th:each="employee : ${employeeList}">
<td th:text="${employee.id}"></td>
<td th:text="${employee.lastName}"></td>
<td th:text="${employee.email}"></td>
<td th:text="${employee.gender}"></td>
<td>
<a class="deleteA" @click="deleteEmployee"
   // 这里拼接路径和参数
th:href="@{'/employee/'+${employee.id}}">delete</a>
<a th:href="@{'/employee/'+${employee.id}}">update</a>
</td>
</tr>
</table>
</body>
</html>
<!-- 作用:通过超链接控制表单的提交,将post请求转换为delete请求 -->
<form id="delete_form" method="post"

5、具体功能:删除

a>创建处理delete请求方式的表单

<!-- 作用:通过超链接控制表单的提交,将post请求转换为delete请求 -->
<form id="delete_form" method="post">
<!-- HiddenHttpMethodFilter要求:必须传输_method请求参数,并且值为最终的请求方式 -->
<input type="hidden" name="_method" value="delete"/>
</form>

b>删除超链接绑定点击事件

引入vue.js (也可以引入cdn)

<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>

删除超链接

<a class="deleteA" @click="deleteEmployee"
th:href="@{'/employee/'+${employee.id}}">delete</a>

通过vue处理点击事件

<script type="text/javascript">
var vue = new Vue({
el:"#dataTable",
    
methods:{
//event表示当前事件
deleteEmployee:function (event) {
//通过id获取表单标签
var delete_form = document.getElementById("delete_form");
//将触发事件的超链接的href属性为表单的action属性赋值
delete_form.action = event.target.href;
//提交表单
delete_form.submit();
//阻止超链接的默认跳转行为
event.preventDefault();
}
}
});
</script>

c>控制器方法 (delete请求)

@RequestMapping(value = "/employee/{id}", method = RequestMethod.DELETE)
public String deleteEmployee(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/employee";
}

注:这里要是是使用的引入vue.js到/static/js/vue.js,可能会出现,这个static包没有导进war包下

还有可能前端控制器会找不到这个静态资源,而出错

需要在核心配置文件中 开放对静态资源的访问

mvc:default-servlet-handler/

6、具体功能:保存

在最初的表达的操作上加一个超链接,跳转到保存的位置,这个时候只需要跳转不需要操作数据,所以用视图控制器

<th>操作 <a th:href="@{/toadd}">add</a></th>

a>配置view-controller (视图控制器)

mvc:view-controller path="/toAdd" view-name="employee_add"></mvc:viewcontroller>

b>创建employee_add.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Add Employee</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
lastName:<input type="text" name="lastName"><br>
email:<input type="text" name="email"><br>
gender:<input type="radio" name="gender" value="1">male
<input type="radio" name="gender" value="0">female<br>
<input type="submit" value="add"><br>
</form>
</body>
</html

c>控制器方法

@RequestMapping(value = "/employee", method = RequestMethod.POST)
public String addEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";
}

7丶修改

回显数据

a>修改超链接

<a th:href="@{'/employee/'+${employee.id}}">update</a>

b>控制器方法

@RequestMapping(value = "/employee/{id}", method = RequestMethod.GET)
public String getEmployeeById(@PathVariable("id") Integer id, Model model){
Employee employee = employeeDao.get(id);
model.addAttribute("employee", employee);
return "employee_update";
}

c>创建employee_update.html

<form th:action="@{/employee}" method="post">
<input type="hidden" name="_method" value="put">
<input type="hidden" name="id" th:value="${employee.id}">
lastName:<input type="text" name="lastName" th:value="${employee.lastName}">
<br>
email:<input type="text" name="email" th:value="${employee.email}"><br>
<!--
th:field="${employee.gender}"可用于单选框或复选框的回显
若单选框的value和employee.gender的值一致,则添加checked="checked"属性
-->
gender:<input type="radio" name="gender" value="1"
th:field="${employee.gender}">male
<input type="radio" name="gender" value="0"
th:field="${employee.gender}">female<br>
<input type="submit" value="update"><br>
</form>

更新跳转 (PUT请求)

@RequestMapping(value = "/employee", method = RequestMethod.PUT)
public String updateEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";
}

8丶对静态资源的总结

  • Tomcat中的web.xml和项目中的web.xml,项目中有默认的defult控制器,当项目中有前端控制器时,会有就近原则,走前端控制器

  • 首先我们得前端控制器处理资源,是通过在Ioc容器的控制器再与请求地址匹配,那么静态资源我们没有这个映射这个地址,所以这个时候就访问不到,所以加上注解

  • 反正没事下面注解都加上就可以了

        <!--  开启对静态资源的访问(当前端控制器找不到,会再走默认的控制器)  -->
            <mvc:default-servlet-handler/>
        // 如果不加   开启mvc注解驱动的标签 就只会走默认的控制器了
        // 只有两个都加,才会在 前端控制器 找不到 的情况下走 默认控制器
        <!--    开启mvc注解驱动的标签-->
        <mvc:annotation-driven/>
    

10.HttpMessageConverter

  • HttpMessageConverter,报文信息转换器,将请求报文转换为Java对象,或将Java对象转换为响应报文

  • HttpMessageConverter提供了两个注解和两个类型:

  • @RequestBody,

  • @ResponseBody

  • RequestEntity

  • ResponseEntity

1、@RequestBody

@RequestBody可以获取请求体,需要在控制器方法设置一个形参,使用@RequestBody进行标识,当 前请求的请求体就会为当前注解所标识的形参赋值

<form th:action="@{/testRequestBody}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit">
</form>
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String requestBody){
System.out.println("requestBody:"+requestBody);
return "success";
}

输出结果: requestBody:username=admin&password=123456

2、RequestEntity (常用)

RequestEntity封装请求报文的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的 请求报文就会赋值给该形参,可以通过getHeaders()获取请求头信息,通过getBody()获取请求体信息

@RequestMapping("/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity){
System.out.println("requestHeader:"+requestEntity.getHeaders());
System.out.println("requestBody:"+requestEntity.getBody());
return "success";
}

3、@ResponseBody(常)

  • 请求和响应

    • 当请求或者响应时,请求头和响应头是一些配置等,而请求体和响应体才是我们前后台互传的数据
    • 转发和重定向到一个页面,也就是,响应体里有一个页面
  • 之前我们是使用HttpServletResponse 往前台响应

    ResponseBody.getwriter().print()等等
    
  • @ResponseBody用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到 浏览器 ,也就是说,不会走thy视图解析器了,会直接把数据响应过去

@RequestMapping("/Respond")
    @ResponseBody
    public String Respond() throws IOException {
      return "hello,ResponseBody";
    }

4、@RestController注解(常)

@RestController注解是springMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了 @Controller注解,并且为其中的每个方法添加了@ResponseBody注解

5、ResponseEntity

ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文 ,用来做下载文件

11.文件的上传和下载

1丶下载

  • 使用ResponseEntity实现下载文件的功能
  • 我们要做的就改改文件的路径
@RequestMapping("/testDown")
    public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws
            IOException {
//获取ServletContext对象
        ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
        String realPath = servletContext.getRealPath("/static/js/1.png");
//创建输入流
        InputStream is = new FileInputStream(realPath);
//创建字节数组
        byte[] bytes = new byte[is.available()];
//将流读到字节数组中
        is.read(bytes);
//创建HttpHeaders对象设置响应头信息
        MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字
        headers.add("Content-Disposition", "attachment;filename=1.png");
//设置响应状态码
        HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
        ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers,
                statusCode);
//关闭输入流
        is.close();
        return responseEntity;
    }

2、文件上传

文件上传要求form表单的请求方式必须为post,并且添加属性enctype="multipart/form-data" SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息

上传步骤:

a>添加依赖:

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload --
>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>

b>在SpringMVC的配置文件中添加配置:

<!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>

c>前端

<form th:action="@{/testUp}" method="post" enctype="multipart/form-data">
    <input type="file" name="photo">
    <input type="submit" value="上传">
</form>

d>控制器方法:

@RequestMapping("/testUp")
public String testUp(MultipartFile photo, HttpSession session) throws
IOException {
//获取上传的文件的文件名
String fileName = photo.getOriginalFilename();
//处理文件重名问题
String hzName = fileName.substring(fileName.lastIndexOf("."));
fileName = UUID.randomUUID().toString() + hzName;
//获取服务器中photo目录的路径
ServletContext servletContext = session.getServletContext();
String photoPath = servletContext.getRealPath("photo");
File file = new File(photoPath);
if(!file.exists()){
file.mkdir();
}
String finalPath = photoPath + File.separator + fileName;
//实现上传功能
photo.transferTo(new File(finalPath));
return "success";
}

11.JSON

1丶什么是JSON

  • JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛。
  • 采用完全独立于编程语言的文本格式来存储和表示数据。
  • 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。
  • 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

2丶JavaScript里的JSON

  • 花括号,键值对,逗号隔开,冒号隔开键和值,除了键也是字符串,其他跟js对象一样
  • stringify:对象转字符串
  • parse:字符串转对象
    <script type="text/javascript">
        let jj={
            name:"lc",
            age:20,
            id:22
        }
        console.log(jj);
       //对象转字符串
       let st=JSON.stringify(jj)
        console.log(st)
       // 字符串转对象
       let jj2 =JSON.parse(st)
    </script>

3丶在java中的JSON

1丶使用Springmvc时json

a>导入jackson的依赖

b>在SpringMVC的核心配置文件中开启mvc的注解驱动,此时在HandlerAdaptor中会自动装配一个消 息转换器:MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为Json格 式的字符串

<mvc:annotation-driven />

c>在处理器方法上使用@ResponseBody注解进行标识

d>将Java对象直接作为控制器方法的返回值返回,就会自动转换为Json格式的字符串

@RequestMapping("/testResponseUser")
@ResponseBody
public User testResponseUser(){
return new User(1001,"admin","123456",23,"男");
}

浏览器的页面中展示的结果: {"id":1001,"username":"admin","password":"123456","age":23,"sex":"男"}

2丶使用jackson

- 1.导入jackson的包
  
  ```xml
  <dependency>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
              <version>1.18.12</version>
              <scope>provided</scope>
          </dependency>
  ```

- 2.使用注解:**@ResponseBody**使用后,这个方法返回的值就**不会经过视图解析器**,就单纯**返回一个字符串**
  
- 3.创建jackson对象
  
- 4.使用jackson方法并将需要传入的对象(集合也可以)转换成json字符串
  
- 5.处理json字符串的乱码
  
  - 单独处理:@RequestMapping(value = "/json1",produces = "application/json;charset=utf-8")
  - 统一处理:
  
  ```xml
  <mvc:annotation-driven>
     <mvc:message-converters register-defaults="true">
         <bean class="org.springframework.http.converter.StringHttpMessageConverter">
             <constructor-arg value="UTF-8"/>
         </bean>
         <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
             <property name="objectMapper">
                 <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                     <property name="failOnEmptyBeans" value="false"/>
                 </bean>
             </property>
         </bean>
     </mvc:message-converters>
  </mvc:annotation-driven>
  ```

  ```java
  @Controller
  //@RestController,当使用@RestController时下面的所有方法都会返回字符串不经过视图解析器,不需要@ResponseBody
  public class JsonController {
  //@ResponseBody使用后,这个方法返回的值就不会经过视图解析器,就单纯返回一个字符串
       @ResponseBody
       @RequestMapping(value = "/json1",produces = "application/json;charset=utf-8")
      public String json1() throws JsonProcessingException {
  //        创建一个jackson的对象映射器,用来解析数据
          ObjectMapper mapper = new ObjectMapper();
  //        创建传入的对象
          User user = new User(1,"刘超","123");
       //日期格式   Date d = new data();
  //        将我们的对象解析成json格式
          String s = mapper.writeValueAsString(user);
           String s = mapper.writeValueAsString(d);
          return s;
      }
  }
  
  ```

  6.提取成工具类
  
  ```java
  public class ControllerUtlis {
      public String json(User user){
          return json(user,null);
      }
      
      public String json(User user,String dateFormat){
  //        创建一个jackson的对象映射器,用来解析数据
          ObjectMapper mapper = new ObjectMapper();
          Date date = new Date();
          SimpleDateFormat format = new SimpleDateFormat(dateFormat);
          mapper.setDateFormat(format);
  //        创建传入的对象
          user = new User(1,"刘超","123");
  //        将我们的对象解析成json格式
          String s = null;
          try {
              return mapper.writeValueAsString(user);
          } catch (JsonProcessingException e) {
              e.printStackTrace();
          }
          return null;
      }
  }
  ```

3丶使用 FastJson (阿里巴巴)

- 导包
  
- astjson 三个主要的类:
  
  **JSONObject  代表 json 对象** 
  
  - JSONObject实现了Map接口, 猜想 JSONObject底层操作是由Map实现的。
  - JSONObject对应json对象,通过各种形式的get()方法可以获取json对象中的数据,也可利用诸如size(),isEmpty()等方法获取"键:值"对的个数和判断是否为空。其本质是通过实现Map接口并调用接口中的方法完成的。
  
  **JSONArray  代表 json 对象数组**
  
  - 内部是有List接口中的方法来完成操作的。
  
  **JSON代表 JSONObjectJSONArray的转化**
  
  - JSON类源码分析与使用
  - 仔细观察这些方法,主要是实现json对象,json对象数组,javabean对象,json字符串之间的相互转化。
  • 常用方法:

    • toJSONString:Java对象 转 JSON字符串
    • parseObject:JSON字符串 转 Java对象
    • toJSON:Java对象 转 JSON对象
    • toJavaObject:JSON对象 转 Java对象
public class FastJsonDemo {
   public static void main(String[] args) {
       //创建一个对象
       User user1 = new User("1号", 3, "男");
       User user2 = new User("2号", 3, "男");
       User user3 = new User("3号", 3, "男");
       User user4 = new User("4号", 3, "男");
       List<User> list = new ArrayList<User>();
       list.add(user1);
       list.add(user2);
       list.add(user3);
       list.add(user4);

       System.out.println("*******Java对象 转 JSON字符串*******");
       String str1 = JSON.toJSONString(list);
       System.out.println("JSON.toJSONString(list)==>"+str1);
       String str2 = JSON.toJSONString(user1);
       System.out.println("JSON.toJSONString(user1)==>"+str2);

       System.out.println("\n****** JSON字符串 转 Java对象*******");
       User jp_user1=JSON.parseObject(str2,User.class);
       System.out.println("JSON.parseObject(str2,User.class)==>"+jp_user1);

       System.out.println("\n****** Java对象 转 JSON对象 ******");
       JSONObject jsonObject1 = (JSONObject) JSON.toJSON(user2);
       System.out.println("(JSONObject) JSON.toJSON(user2)==>"+jsonObject1.getString("name"));

       System.out.println("\n****** JSON对象 转 Java对象 ******");
       User to_java_user = JSON.toJavaObject(jsonObject1, User.class);
       System.out.println("JSON.toJavaObject(jsonObject1, User.class)==>"+to_java_user);
  }
}

12.SpringMVC处理ajax

a>请求超链接:

<div id="app">
<a th:href="@{/testAjax}" @click="testAjax">testAjax</a><br>
</div

b>通过vue和axios处理点击事件:

<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
<script type="text/javascript" th:src="@{/static/js/axios.min.js}"></script>
<script type="text/javascript">
var vue = new Vue({
el:"#app",
methods:{
testAjax:function (event) {
axios({
method:"post",
url:event.target.href,
params:{
username:"admin",
password:"123456"
}
}).then(function (response) {
alert(response.data);
});
event.preventDefault();
}
}
});
</script>

c>控制器方法:

@RequestMapping("/testAjax")
@ResponseBody
public String testAjax(String username, String password){
System.out.println("username:"+username+",password:"+password);
return "hello,ajax";
}

13.拦截器

1丶过滤器与拦截器的区别:

拦截器是AOP思想的具体应用。

过滤器

  • servlet规范中的一部分,任何java web工程都可以使用
  • 在url-pattern中配置了/*之后,可以对所有要访问的资源进行拦截

拦截器

  • 拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用
  • 拦截器只会拦截访问的控制器方法, 如果访问的是jsp/html/css/image/js是不会进行拦截的

2、拦截器的配置

  • SpringMVC中的拦截器用于拦截控制器方法的执行
  • SpringMVC中的拦截器需要实现HandlerInterceptor ,并重写方法
public class myIntercepter implements HandlerInterceptor {
//    控制器方法执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        return true;
    }
//控制器方法之后
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
//渲染视图后执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}
  • SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置
<!--    配置拦截器-->
    <mvc:interceptors>
<!--        <bean class="com.LiuDeHen.interceptors.myIntercepter"/>-->
<!--    <ref bean="myIntercepter"/>-->
        <!-- 以上两种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截 -->
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
<!--         /** 是所有请求   -->
            <mvc:exclude-mapping path="/lanjieq"/>
            <ref bean="myIntercepter"/>
        </mvc:interceptor>
        <!--
以上配置方式可以通过ref或bean标签设置拦截器,通过mvc:mapping设置需要拦截的请求,通过
mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求
-->
    </mvc:interceptors>
  • 此时我们运行结果结论
    • 当我们自己定义的拦截器返回为true时,为放行
    • 此时我们配置的xml文件,/对所有的文件都放行**,也就是都会把拦截器三个都输出来
    • 排除/lanjieq这一个路径,这个路径就不会放行,三句话就会输出第一句
  • 为什么返回值为true为放行,false为拦截

3丶源码分析

  • 进入DispatcherServlet

  • 找到

     if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                            return;
                        }
    
  • 再点进applyPreHandle方法

     boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
            for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
                HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
                if (!interceptor.preHandle(request, response, this.handler)) {
                    //拦截器的preHandle(控制器之前执行的方法),当返回为false时才会进入,并且返回false,这个返回的结果再丢到if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                            return;
                        }
                //此时return直接返回结束,所以拦截器的preHandle返回为false会直接返回
                    this.triggerAfterCompletion(request, response, (Exception)null);
                    return false;
                }
            }
    
  • 得出结论,false为拦截,true为放行

4、拦截器的三个抽象方法

SpringMVC中的拦截器有三个抽象方法:

preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返 回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法

postHandle:控制器方法执行之后执行postHandle()

afterComplation:处理完视图和模型数据,渲染视图完毕之后执行afterComplation( )

5、多个拦截器的执行顺序

a>若每个拦截器的preHandle()都返回true 此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关: preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行

b>若某个拦截器的preHandle()返回了false , preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false 的拦截器之前的拦截器的afterComplation()会执行

6丶源码分析

  • 断点打到控制器方法的返回页面上

  • 调试,浏览器发送的请求进入到那个断点的方法上

  • 找到方法栈上,doDispatch,进入,到了DispatcherServlet

  • 分别将这几个方法打上断点

    // prehandel控制器处理前的方法
    //mappedHandler 映射处理器,也就是我们当前的控制方法
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                            return;
                        }
    
    //  会将请求和响应的数据封装到model,跳转到view (modelAndview)
     mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    
    // PostHandle控制器处理后方法
     mappedHandler.applyPostHandle(processedRequest, response, mv);
    
    // 渲染视图方法
      this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
    
    
     if (mappedHandler != null) {
                mappedHandler.triggerAfterCompletion(request, response, ex);
            }
    
  • 首先进入applyPreHandle

       boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
           //循环遍历我们的拦截器
            for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
                HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
                if (!interceptor.preHandle(request, response, this.handler)) {
                    //如果拦截器的prehandel返回值的false就结束,所以就拦截成功了,不会执行传modelAndview,控制器后的PostHandle,和渲染方法
                    this.triggerAfterCompletion(request, response, (Exception)null);
                    return false;
                }
            }
    
  • 退出上一个方法,进入applyPostHandle

    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
        //反向遍历,所以才会有多个拦截器时,先执行的PreHandle的拦截器就会比其他拦截器后执行PostHandle
            for(int i = this.interceptorList.size() - 1; i >= 0; --i) {
                HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
                interceptor.postHandle(request, response, this.handler, mv);
            }
    
    
  • 再进入processDispatchResult(渲染视图)

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception
        //会讲请求,响应,和modelAndview 等作为参数渲染视图
    

14.异常处理器

1、基于配置的异常处理

SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver HandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver和 SimpleMappingExceptionResolver

SpringMVC提供了自定义的异常处理器SimpleMappingExceptionResolver,使用方式:

  • 定义出现什么异常
  • 跳转到那个视图
  • 传异常参数到请求域
 <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings" >
            <props>
     <!--
			properties的键表示处理器方法执行过程中                   出现的异常(全类名)
			properties的值表示若出现指定异常时,设置一个新的视图名称,   跳转到指定页面(会经过视图解析器)
-->
                <prop key="java.lang.ArithmeticException">error</prop>
            </props>
        </property>
     <!--
			exceptionAttribute属性设置一个属性名,将出现的异常信息在     请求域中进行共享(可以在错误页面拿到这个异常信息)
-->

        <property name="exceptionAttribute" value="error"></property>
    </bean>

2、基于注解的异常处理

@ControllerAdvice
//点进去看到 这个注解里有@Component,所以是一个扩展注解,也能将我们的类注册为bean组件
public class errController {

    @ExceptionHandler(ArithmeticException.class)
    public String err(Exception ex, Model model){
                //ex表示当前请求处理中出现的异常对象
        model.addAttribute("ex",ex);
        return "error";
    }
}

15.注解配置SpringMVC

使用配置类和注解代替web.xml和SpringMVC配置文件的功能

1、创建初始化类,代替web.xml

在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类, 如果找到的话就用它来配置Servlet容器。 Spring提供了这个接口的实现,名为 SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配 置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,名为 AbstractAnnotationConfigDispatcherServletInitializer,当我们的类扩展了 AbstractAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器的时候,容器会自 动发现它,并用它来配置Servlet上下文

  • webInit类
public class WebInit extends
AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定spring的配置类
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
/**
* 指定SpringMVC的配置类
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
/**
* 指定DispatcherServlet的映射规则,即url-pattern
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 添加过滤器
* @return
*/
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
encodingFilter.setForceRequestEncoding(true);
HiddenHttpMethodFilter hiddenHttpMethodFilter = new
HiddenHttpMethodFilter();
return new Filter[]{encodingFilter, hiddenHttpMethodFilter};
}
}

2、创建SpringConfig配置类,代替spring的配置文件

@Configuration
public class SpringConfig {
//ssm整合之后,spring的配置信息写在此类中
}

3、创建WebConfig配置类

代替springMVC 的配置文件:

  1. 扫描组件

  2. 视图解析器

  3. view-controller视图控制器

  4. default-servlet-handlerI(静态资源处理)

  5. mvc注解驱动

  6. 文件上传解析器

  7. 异常处理

  8. 拦截器

@Configuration
//扫描组件
@ComponentScan("com.LiuDeHen.controller")
//开启MVC注解驱动
@EnableWebMvc
 class WebConfig implements WebMvcConfigurer {
    //使用默认的servlet处理静态资源

     //**default-servlet-handlerI(静态资源处理)**
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
    //配置文件上传解析器
    @Bean
    public CommonsMultipartResolver multipartResolver(){
        return new CommonsMultipartResolver();
    }
    //配置拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        FirstInterceptor firstInterceptor = new FirstInterceptor();
        registry.addInterceptor(firstInterceptor).addPathPatterns("/**");
    }

//配置视图控制
@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("index");
}
     
//配置异常映射
@Override
public void
configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties prop = new Properties();
prop.setProperty("java.lang.ArithmeticException", "error");
//设置异常映射
exceptionResolver.setExceptionMappings(prop);
//设置共享异常信息的键
exceptionResolver.setExceptionAttribute("ex");
resolvers.add(exceptionResolver);
}
     
//配置生成模板解析器
@Bean
public ITemplateResolver templateResolver() {
    WebApplicationContext webApplicationContext =
            ContextLoader.getCurrentWebApplicationContext();
// ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
    ServletContextTemplateResolver templateResolver = new
            ServletContextTemplateResolver(
            webApplicationContext.getServletContext());
    templateResolver.setPrefix("/WEB-INF/templates/");
    templateResolver.setSuffix(".html");
    templateResolver.setCharacterEncoding("UTF-8");
    templateResolver.setTemplateMode(TemplateMode.HTML);
    return templateResolver;
}
    //生成模板引擎并为模板引擎注入模板解析器
    @Bean
    public SpringTemplateEngine templateEngine(ITemplateResolver
                                                       templateResolver) {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        return templateEngine;
    }
    //生成视图解析器并未解析器注入模板引擎
    @Bean
    public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setTemplateEngine(templateEngine);
        return viewResolver;
    }

    private class FirstInterceptor implements HandlerInterceptor {
    }
}

16.SpringMVC执行流程

1、SpringMVC常用组件

  • DispatcherServlet
    • 前端控制器,不需要工程师开发,由框架提供
    • 作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求
  • HandlerMapping
    • 处理器映射器,不需要工程师开发,由框架提供
    • 用处:根据请求url和请求的方式method查找相对应的Handler(处理器/我们定义的控制方法)进行映射
  • Handler
  • 处理器需要工程师开发 ,就是我们定义的控制方法
  • 作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理
  • HandlerAdapter
    • 处理器适配器,不需要工程师开发,由框架提供
    • 作用:通过HandlerAdapter对处理器(控制器方法)进行执行,也就是执行我们控制器里的方法,把数据和跳转视图封装成modelAndView
    • 我们在前面源码多次看到的 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 这ha就是HandlerAdapter
  • ViewResolver
    • 视图解析器,不需要工程师开发,由框架提供
    • 作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView(默认转发视图)、 RedirectView (默认重定向视图),跟据是否有前缀来判断是哪个视图,然后进行页面渲染
  • View
    • 视图
    • 作用:页面渲染完毕后,将模型数据通过页面展示给用户

2、DispatcherServlet初始化过程

DispatcherServlet 本质上是一个 Servlet,所以天然的遵循 Servlet 的生命周期。所以宏观上是 Servlet 生命周期来进行调度。

  • 初始化流程的继承关系(最后GenericServlet实现了Servlet接口)

    1647582120605

  • 执行的方法流程

    1647582224767

  • a>初始化WebApplicationContext

    org.springframework.web.servlet.FrameworkServlet 里的initServletBean里我们可以看到整个初始化方法initWebApplicationContext()

    this.webApplicationContext = this.initWebApplicationContext();
                this.initFrameworkServlet();
    
  • 点进去

     protected WebApplicationContext initWebApplicationContext() {
            WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
            WebApplicationContext wac = null;
         //1.由于是第一次进来,所以肯定为null
            if (this.webApplicationContext != null) {
                wac = this.webApplicationContext;
                if (wac instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
                    if (!cwac.isActive()) {
                        if (cwac.getParent() == null) {
                            cwac.setParent(rootContext);
                        }
    
                        this.configureAndRefreshWebApplicationContext(cwac);
                    }
                }
            }
    //2.==null执行findWebApplicationContext也找不到,因为没有创建过
            if (wac == null) {
                wac = this.findWebApplicationContext();
            }
    //3.创建WebApplicationContext
            if (wac == null) {
                wac = this.createWebApplicationContext(rootContext);
            }
    //4.刷新WebApplicationContext
            if (!this.refreshEventReceived) {
                synchronized(this.onRefreshMonitor) {
                    this.onRefresh(wac);
                }
            }
    //5.创建ServletContext应用上下文,将IOC容器在应用域共享
            if (this.publishContext) {
                String attrName = this.getServletContextAttributeName();
                this.getServletContext().setAttribute(attrName, wac);
            }
    
            return wac;
        }
    
    
  • b>创建WebApplicationContext

点进创建WebApplicationContext的方法createWebApplicationContext

protected WebApplicationContext createWebApplicationContext(@Nullable
ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass))
{
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" +
getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
// 通过反射创建 IOC 容器对象
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext)
BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// 将spring的IOC设置成Springmvc的Ioc的父容器
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}

  • c>DispatcherServlet初始化策略

    FrameworkServlet创建WebApplicationContext后,刷新容器,调用onRefresh(wac),此方法在 DispatcherServlet中进行了重写调用了initStrategies(context)方法,初始化策略,即初始化 DispatcherServlet的各个组件

    所在类:org.springframework.web.servlet.DispatcherServlet

protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}

3、DispatcherServlet调用组件处理请求

流程跟初始化一样,从Servlet接口开始找service()执行方法

  • 找到FrameworkServlet里的service()时
   protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
            super.service(request, response);
        } else {
            this.processRequest(request, response);
        }
  • 进入processRequest()执行请求

  • 找到里面的这句代码

      this.doService(request, response);
    
  • 进入doService()执行服务

  • 然后到了DispatcherServlet里的doService()方法

  • 向下看,找到doDispatch方法

    doDispatch(request, response)
    
  • 这里是不是很熟悉了,点进去

    protected void doDispatch(HttpServletRequest request, HttpServletResponse
                              response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
                // Determine handler for the current request.
                /*
    mappedHandler:调用链
    包含handler、interceptorList、interceptorIndex
    handler:浏览器发送的请求所匹配的控制器方法
    interceptorList:处理控制器方法的所有拦截器集合
    interceptorIndex:拦截器索引,控制拦截器afterCompletion()的执行
    */
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
                // Determine handler adapter for the current request.
                // 通过控制器方法创建相应的处理器适配器,调用所对应的控制器方法
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request,
                                                           mappedHandler.getHandler());
                    if (new ServletWebRequest(request,
                                              response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                // 调用拦截器的preHandle()
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                // Actually invoke the handler.
                // 由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
                mv = ha.handle(processedRequest, response,
                               mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);
                // 调用拦截器的postHandle()
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                // As of 4.3, we're processing Errors thrown from handler methods as
                well,
                // making them available for @ExceptionHandler methods and other
                scenarios.
                    dispatchException = new NestedServletException("Handler dispatch
                                                                   failed", err);
                                                                   }
                                                                   // 后续处理:处理模型数据和渲染视图
                                                                   processDispatchResult(processedRequest, response, mappedHandler, mv,
                                                                                         dispatchException);
                                                                   }
                                                                   catch (Exception ex) {
                                                                       triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
                                                                   }
                                                                   catch (Throwable err) {
                                                                       triggerAfterCompletion(processedRequest, response, mappedHandler,
                                                                                              new NestedServletException("Handler processing
                                                                                                                         failed", err));
                                                                                                                         }
                                                                                                                         finally {
                                                                                                                             if (asyncManager.isConcurrentHandlingStarted()) {
                                                                                                                                 // Instead of postHandle and afterCompletion
                                                                                                                                 if (mappedHandler != null) {
                                                                                                                                     mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                                                                                                                                 }
                                                                                                                             }
                                                                                                                             else {
                                                                                                                                 // Clean up any resources used by a multipart request.
                                                                                                                                 if (multipartRequestParsed) {
                                                                                                                                     cleanupMultipart(processedRequest);
                                                                                                                                 }
                                                                                                                             }
                                                                                                                         }
                                                                                                                         }
    
  • processDispatchResult() 方法

private void processDispatchResult(HttpServletRequest request,
                                   HttpServletResponse response,
                                   @Nullable HandlerExecutionChain
                                   mappedHandler, @Nullable ModelAndView mv,
                                   @Nullable Exception exception) throws
    Exception {
    boolean errorView = false;
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered",
                         exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler()
                              : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }
    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
        // 处理模型数据和渲染视图
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isTraceEnabled()) {
            logger.trace("No view rendering, null ModelAndView returned.");
        }
    }
    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }
    if (mappedHandler != null) {
        // Exception (if any) is already handled..
        // 调用拦截器的afterCompletion()
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

4、SpringMVC的执行流程

  1. 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。

  2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射(@RequestMapping):

     a) **不存在** 
    

​ i. 再判断是否配置了mvc:default-servlet-handler(静态资源过滤器)

​ ii. 如果没配置,则控制台报映射查找不到,客户端展示404错误

1647588261919

b) 存在则执行下面的流程

  1. 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及 Handler对象对应的拦截器的集合和index),最后以HandlerExecutionChain执行链对象的形式返回。

​ 4. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter(适配器)。

​ 5. 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】

  1. 提取Request中的模型数据填充Handler入参,开始执行Handler(Controller)方法,处理请求。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

​ a) HttpMessageConveter将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定 的响应信息

​ b) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等

​ c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等

​ d) 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中

  1. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象

  2. 此时将开始执行拦截器的postHandle(...)方法【逆向】。

        9. 根据返回的**ModelAndView**(此时会判断**是否存在异常**:如果**存在异常,则执行 HandlerExceptionResolver进行异常处理**)选择一个**适合的ViewResolver进行视图解析****根据Model 和View,来渲染视图。** 
        10.   渲染视图完毕执行拦截器的**afterCompletion**(…)方法【逆向】。  
        11.  **将渲染结果返回给客户端。** 
    

17.整合SSM

1丶mybatis层

  • 数据库搭建

  • 环境的配置

    <!--    导入junit,mysql,数据库连接池,servlet,jsp,,mybatis,mybatis-spring,spring-->
        <dependencies>
            <!--Junit-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
            <!--数据库驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
            <!-- 数据库连接池 -->
            <dependency>
                <groupId>com.mchange</groupId>
                <artifactId>c3p0</artifactId>
                <version>0.9.5.2</version>
            </dependency>
    
            <!--Servlet - JSP -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
                <version>2.5</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet.jsp</groupId>
                <artifactId>jsp-api</artifactId>
                <version>2.2</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>jstl</artifactId>
                <version>1.2</version>
            </dependency>
    
            <!--Mybatis-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.2</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>2.0.2</version>
            </dependency>
    
            <!--Spring-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.1.9.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.1.9.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.12</version>
            </dependency>
        </dependencies>
    
    <!--资源过滤器-->
        <build>
            <resources>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>false</filtering>
                </resource>
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>false</filtering>
                </resource>
            </resources>
        </build>
    
  • 基本框架的构建

    • Dao
    • pojo
    • Service
    • controller
    • resource下:
      • mybatis的xml文件
      • spring的xml文件
      • 连接数据库的propertis文件
  • mybatis层的编写

    • Dao层

      • mapper接口

        public interface bookMapper {
        //    增加一本书
            int addBook(Books books);
        //    删除一本书
            int deleteBook(@Param("bookID") int id);
        //    修改一本书
            int updateBook(Books books);
        //    查询一本书
            Books selectBook(@Param("bookName") String Bookname);
        //    查询所有书
            List<Books> selectBooks();
        }
        
      • mapper.xml文件(别忘记注册)

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <!--一共mapper对应一个接口-->
      <mapper namespace="com.LiuDeHen.Dao.bookMapper">
          <insert id="addBook" parameterType="Books">
              insert into ssmbuild.books values(#{bookID},#{bookName},#{bookCounts},#{detail})
          </insert>
      
          <delete id="deleteBook" parameterType="Int">
              delete from ssmbuild.books
              where bookID=#{bookID};
          </delete>
      
          <update id="updateBook" parameterType="Books">
              update ssmbuild.books set bookName=#{bookName}
              where bookID=#{bookID};
          </update>
      
          <select id="selectBook" resultType="Books">
              select * from ssmbuild.books
              where bookID=#{bookID}
          </select>
      
          <select id="selectBooks" resultType="Books">
              select * from ssmbuild.books
          </select>
      <!--    写完别忘了注册Mapper-->
      </mapper>
      
    • service层

      • service接口(跟mapper接口差不多)

        public interface bookService {
            //    增加一本书
            int addBook(Books books);
            //    删除一本书
            int deleteBook(int id);
            //    修改一本书
            int updateBook(Books books);
            //    查询一本书
            Books selectBook(String Bookname);
            //    查询所有书
            List<Books> selectBooks();
        }
        
      • service的实现类(调用Dao层)

        public class bookServiceIplm implements bookService{
        //    业务层调用Dao层
            private bookMapper bm;
            public bookServiceIplm(bookMapper bookMapper){
                bm=bookMapper;
            }
            @Override
            public int addBook(Books books) {
                return bm.addBook(books);
            }
        
            @Override
            public int deleteBook(int id) {
                return bm.deleteBook(id);
            }
        
            @Override
            public int updateBook(Books books) {
                return bm.updateBook(books);
            }
        
            @Override
            public Books selectBook(String Bookname) {
                return bm.selectBook(Bookname);
            }
        
            @Override
            public List<Books> selectBooks() {
                return bm.selectBooks();
            }
        }
        
    • resource层

      • mybatis.xml(起别名和注册mapper)

        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
                "http://mybatis.org/dtd/mybatis-3-config.dtd">
        
    ``` - propertis

jdbc.driver=com.mysql.jdbc.Driver
  jdbc.url=jdbc:mysql://localhost:3306/ssmbuild?useSSL=true&useUnicode=true&characterEncoding=utf8
  jdbc.username=root
  jdbc.password=123

- **pojo层**
  - 实体类
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Books {
        private int bookID;
        private String bookName;
        private int bookCounts;
        private String detail;
    }
```

10.2丶Spirng层

  • spring-dao.xml(spring整合mybatis,这里使用数据源cp30)

    <?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">
    
    <!--1.关联数据库配置文件    -->
    <context:property-placeholder location="classpath:datesource.properties"/>
    
        <!--2.连接池
    dbcp:半自动操作,不能自动连接
    cp30:自动化操作
    druid:
    hikari(springboot默认数据源):
    -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="${jdbc.driver}"/>
            <property name="jdbcUrl" value="${jdbc.url}"/>
            <property name="user" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
    
    <!--3.sqlSessionFactory    -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"/>
    <!--绑定mybatis的配置文件-->
            <property name="configLocation" value="classpath:mybatis-config.xml"/>
        </bean>
    
    <!--配置dao接口扫描包,动态的实现Dao接口可以注入到Spring容器中-->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--注入sqlSessionFactory-->
            <property name="sqlSessionFactoryBeanName" value="dataSource"/>
    <!--扫描dao包-->
            <property name="basePackage" value="com.LiuDeHen.Dao"/>
        </bean>
    </beans>
    
  • spring.service.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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--1.扫描包-->
    <context:component-scan base-package="com.LiuDeHen.service"/>
    <!--2.将我们的业务类,注入到spring中,可以通过配置或注解实现-->
        <bean id="bookServiceIplm" class="com.LiuDeHen.service.bookServiceIplm">
            <property name="bm" ref="bookMapper"/>
        </bean>
    
    <!--3.声明事务-->
        <bean id="TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 注入数据源-->
        <property name="dataSource" ref="dataSource"/>
        </bean>
    
    <!-- 4.事务支持-->
    </beans>
    

10.3丶SpringMVC层

  • 添加框架支持

  • 添加DispathServlet和乱码过滤配置(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">
    
    <!--DispathServlet-->
        <servlet>
            <servlet-name>springmvc</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:springmvc-servlet.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
    <!-- 乱码过滤   -->
        <filter>
            <filter-name>fileter</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>fileter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
    <!--Session-->
        <session-config>
            <session-timeout>15</session-timeout>
        </session-config>
    </web-app>
    
  • springmvc-servlet.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">
    
    <!--1.注解驱动    -->
        <mvc:annotation-driven/>
    <!--2.静态资源的过滤    -->
        <mvc:default-servlet-handler/>
    <!--3.扫描包:controller    -->
        <context:component-scan base-package="com.LiuDeHen.controller"/>
    <!--4.视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <property name="suffix" value=".jsp"/>
        </bean>
    </beans>
    
  • controller控制层

    @Controller
    @RequestMapping("/book")
    public class BookController {
        @Autowired
        @Qualifier("BookServiceImpl")
        private BookService bookService;
    //  查询全部书籍
        @RequestMapping("/allBook")
        public String list(Model model){
            List<Books> list = bookService.queryAllBook();
            model.addAttribute("list",list);
            return "allBook";
        }
    
    //    跳转到增加书籍页面
        @RequestMapping("/toAddBook")
        public String add(){
            return "addBook";
        }
    
    //    添加书籍的请求
        @RequestMapping("/toAddBook2")
        public String add2(Books books,Model model){
            int i = bookService.addBook(books);
            if (i>0){
                return "redirect:/book/allBook";
            }
           return null;
        }
    
    //    跳转到修改书籍
    @RequestMapping("/toUpdateBook")
    public String toUpdateBook(Model model, int id) {
        Books books = bookService.queryBookById(id);
        System.out.println(books);
        model.addAttribute("Books",books );
        return "UpBook";
    }
    //    修改书籍的请求
        @RequestMapping("/toUpdate")
        public String update2(Books books){
            bookService.updateBook(books);
            return "redirect:/book/allBook";
        }
    
    //    删除
        @RequestMapping("/del")
        public String deleteBook(int id){
            bookService.deleteBookById(id);
            return "redirect:/book/allBook";
        }
    }
    
  • 前端

    1646043840091

    • addBook

      <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <html>
      <head>
          <title>新增书籍</title>
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <!-- 引入 Bootstrap -->
          <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
          <style type="text/css">
          .container{
              margin-left: 400px;
              margin-top: 100px;
              width: 300px;
          }
          </style>
      
      </head>
      <body>
      <div class="container">
      
          <div class="row clearfix">
              <div class="col-md-12 column">
                  <div class="page-header">
                      <h1>
                          <small>新增书籍</small>
                      </h1>
                  </div>
              </div>
          </div>
          <form action="${pageContext.request.contextPath}/book/toAddBook2" method="post">
              书籍名称:<input type="text" name="bookName" required><br><br><br>
              书籍数量:<input type="text" name="bookCounts" required><br><br><br>
              书籍详情:<input type="text" name="detail" required><br><br><br>
              <input type="submit" value="添加" style="margin-left: 100px">
          </form>
      </div>
      </body>
      </html>
      
      
    • allBook

      <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <html>
      <head>
          <title>书籍列表</title>
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <!-- 引入 Bootstrap -->
          <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
      </head>
      <body>
      
      <div class="container">
      
          <div class="row clearfix">
              <div class="col-md-12 column">
                  <div class="page-header">
                      <h1>
                          <small>书籍列表 —— 显示所有书籍</small>
                      </h1>
                  </div>
              </div>
          </div>
      
          <div class="row">
              <div class="col-md-4 column">
                  <a class="btn btn-primary" href="${pageContext.request.contextPath}/book/toAddBook">新增</a>
              </div>
          </div>
      
          <div class="row clearfix">
              <div class="col-md-12 column">
                  <table class="table table-hover table-striped">
                      <thead>
                      <tr>
                          <th>书籍编号</th>
                          <th>书籍名字</th>
                          <th>书籍数量</th>
                          <th>书籍详情</th>
                          <th>操作</th>
                      </tr>
                      </thead>
      
                      <tbody>
                      <c:forEach var="book" items="${requestScope.get('list')}">
                          <tr>
                              <td>${book.getBookID()}</td>
                              <td>${book.getBookName()}</td>
                              <td>${book.getBookCounts()}</td>
                              <td>${book.getDetail()}</td>
                              <td>
                                  <a href="${pageContext.request.contextPath}/book/toUpdateBook?id=${book.getBookID()}">更改</a> |
                                  <a href="${pageContext.request.contextPath}/book/del/?id=${book.getBookID()}">删除</a>
                              </td>
                          </tr>
                      </c:forEach>
                      </tbody>
                  </table>
              </div>
          </div>
      </div>
      </body>
      </html>
      
    • UpBook

      <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <html>
      <head>
          <title>书籍列表</title>
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <!-- 引入 Bootstrap -->
          <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
      </head>
      <body>
      
      <div class="container">
      
          <div class="row clearfix">
              <div class="col-md-12 column">
                  <div class="page-header">
                      <h1>
                          <small>书籍列表 —— 显示所有书籍</small>
                      </h1>
                  </div>
              </div>
          </div>
      
          <div class="row">
              <div class="col-md-4 column">
                  <a class="btn btn-primary" href="${pageContext.request.contextPath}/book/toAddBook">新增</a>
              </div>
          </div>
      
          <div class="row clearfix">
              <div class="col-md-12 column">
                  <table class="table table-hover table-striped">
                      <thead>
                      <tr>
                          <th>书籍编号</th>
                          <th>书籍名字</th>
                          <th>书籍数量</th>
                          <th>书籍详情</th>
                          <th>操作</th>
                      </tr>
                      </thead>
      
                      <tbody>
                      <c:forEach var="book" items="${requestScope.get('list')}">
                          <tr>
                              <td>${book.getBookID()}</td>
                              <td>${book.getBookName()}</td>
                              <td>${book.getBookCounts()}</td>
                              <td>${book.getDetail()}</td>
                              <td>
                                  <a href="${pageContext.request.contextPath}/book/toUpdateBook?id=${book.getBookID()}">更改</a> |
                                  <a href="${pageContext.request.contextPath}/book/del/?id=${book.getBookID()}">删除</a>
                              </td>
                          </tr>
                      </c:forEach>
                      </tbody>
                  </table>
              </div>
          </div>
      </div>
      </body>
      </html>
      
posted @   又菜又ai  阅读(47)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示