【21.11.7】SpringMVC

【21.11.7】SpringMVC

1. 入门须知

1.1、知识脉络

  • javaSE:认真学习、老师带

  • javaWeb:认真学习、老师带

  • 框架:研究官方文档、锻炼自学能力、锻炼笔记能力、锻炼项目能力

  • 剩下的java路线:SpringMVC + Vue + SpringBoot + SpringCloud + Linux

 

1.2、重点

 

2. 了解MVC

MVC:(Model)模型(dao,service)、(View)视图(jsp等)、(Controller)控制器(Servlet)

2.1、什么是MVC

image-20211107155442688

 

image-20211107155620645

JSP本质就是一个Servlet!!

image-20211107155712547

image-20211107155928007

面试题:你项目的架构是设计好的,还是演进的?

那肯定是演进的!!随着时代的进步、用户量的增加等、优秀流行框架的层出不从使得我们必须优化我们的项目,使得项目更好、更快地适应当前阶段的运行!!

image-20211107160456807

image-20211107160543174

image-20211107175941067

 

2.2、回顾servlet

(1). 创建maven项目,删除src,让其变成父工程

(2). 在父工程的pom.xml文件上导入所需要的依赖

<dependencies>
   <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.11</version>
   </dependency>
   <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-webmvc</artifactId>
       <version>5.3.12</version>
   </dependency>
   <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
   <dependency>
       <groupId>javax.servlet</groupId>
       <artifactId>javax.servlet-api</artifactId>
       <version>4.0.1</version>
       <scope>provided</scope>
   </dependency>
   <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
   <dependency>
       <groupId>javax.servlet.jsp</groupId>
       <artifactId>javax.servlet.jsp-api</artifactId>
       <version>2.3.3</version>
       <scope>provided</scope>
   </dependency>
   <!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl-api -->
   <dependency>
       <groupId>javax.servlet.jsp.jstl</groupId>
       <artifactId>jstl-api</artifactId>
       <version>1.2</version>
   </dependency>


</dependencies>

(3). 创建子工程项目并让其变成web项目

image-20211107181908498

image-20211107182022188

image-20211107182212691

(4). 查看子项目有没有承接了父项目的依赖,如果没有,那就需要在子工程的pom.xml里重新导入依赖!

image-20211107182526326

(5). 编写一个servlet类

注意点:

  • 获取前端参数:req.getParameter("method")

  • 获得session设置属性:req.getSession().setAttribute("msg","执行了add方法");

  • 携带请求和反馈回来的数据转发视图: req.getRequestDispatcher("/WEB-INF/jsp/test.xml").forward(req,resp);

public class HelloServlet extends HttpServlet {
   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      //1.获取前端参数
       String method =req.getParameter("method");
       if(method.equals("add")){
           req.getSession().setAttribute("msg","执行了add方法");
      }
       if(method.equals("delete")){
           req.getSession().setAttribute("msg","执行了deleter方法");
      }
       //2.调用业务层
       //3.视图转发或者重定向
       //转发:转发到哪个页面
       req.getRequestDispatcher("/WEB-INF/jsp/test.xml").forward(req,resp);
  }

   @Override
   protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       //实现复用
       doGet(req,resp);
  }
}

(6). 创建简单的页面

如果是一些公共可见的页面,可以放在web目录下面;如果是用户不可见的,为了安全起见,放在WEB-INF目录下!!

image-20211107183722237

简单的test.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>Title</title>
</head>
<body>

${meg}

</body>
</html>

form.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>Title</title>
</head>
<body>
<%--把表单提交到hello这个地址,通过post方式提交--%>
<form action="/hello" method="post">
  <%--至少需要两个标签--%>
  <%--这个method会被提交到相应的servlet里处理--%>
   <input type="text" name="method">
  <%--这样才可以提交--%>
   <input type="submit">
</form>
</body>
</html>

(7). 去web.xml注册servlet

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

   <servlet>
       <servlet-name>hello</servlet-name>
       <servlet-class>com.kuang.servlet.HelloServlet</servlet-class>
   </servlet>
   <servlet-mapping>
       <servlet-name>hello</servlet-name>
       <!--跳到相应的servlet文件下-->
       <url-pattern>/hello</url-pattern>
   </servlet-mapping>

   <!--可配置session默认的超时时间,单位为分钟-->
   <!--<session-config>-->
       <!--<session-timeout>15</session-timeout>-->
   <!--</session-config>-->

   <!--&lt;!&ndash;配置欢迎页,默认是跳到index页面&ndash;&gt;-->
   <!--<welcome-file-list>-->
       <!--<welcome-file>index.jsp</welcome-file>-->
   <!--</welcome-file-list>-->

</web-app>

代码流程:首现页面通过表单action=“/hello”配置一个请求,然后这个表单所携带的数据会通过post的方法来到xml配置文件,根据“/hello”找到servlet-mapping映射,再通过servlet-name找到servlet-class,根据servlet-class绑定的类会找到servlet类的所在,从而实现跳转,当跳转到servlet类就会进行相应的业务处理,然后重定向或转发到相应的页面,其中可以通过request、response、session在页面获取数据并进行显示!!

 

(8). 配置tomcat服务器

image-20211107193337214

image-20211107193432590

image-20211107193604442

image-20211107193651811

image-20211107193759107

 

3. 第一个SpringMVC项目

3.1、为什么要学习SpringMVC?

image-20211107202451418

简单原理:

image-20211107203134625

image-20211107203223756

image-20211107203545933

 

3.2、第一个程序hellomvc

(1). 在web.xml里注册dispatchServlet

<?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.注册dispatcherServlet-->
   <servlet>
       <servlet-name>springmvc</servlet-name>
       <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
       <!--关联一个springmvc的配置文件:【servlet-name】-servlet.xml-->
       <init-param>
           <param-name>contextConfigLocation</param-name>
           <!--去找配置文件-->
           <param-value>classpath:springmvc-servlet.xml</param-value>
       </init-param>
       <!--启动级别:1-->
       <load-on-startup>1</load-on-startup>
   </servlet>

   <!--/ 匹配所有请求:(不包括.jsp)-->
   <!--/* 匹配所有请求:(包括.jsp)-->
   <servlet-mapping>
       <servlet-name>springmvc</servlet-name>
       <url-pattern>/</url-pattern>
   </servlet-mapping>
</web-app>

(2). 在spring-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"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">

  <!--添加处理映射器-->
   <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

   <!--添加处理器适配器-->
   <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>

   <!--添加视图解析器:用于页面跳转,DispatcherServlet给他的ModelAndView
       1.获取了ModelAndView的数据
       2.解析了ModelAndView的视图名字
       3.拼接视图名字,找到对应的视图 /WEB-INF/jsp/hello.jsp
       4.将数据渲染到这个视图上-->
   <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
       <!--下面这两个东西:路径和页面名字(没有写死),到时候结合实际页面拼接起来就能
       起到转发页面的效果了,就相当于servlet里的重定向或转发!!-->
       <!--前缀-->
       <property name="prefix" value="/WEB-INF/jsp/"/>
       <!--后缀-->
       <property name="suffix" value=".jsp"/>
   </bean>

   <!--将自己的类交给SpringIOC容器,注册bean-->
   <!--Handler-->
   <bean id="/hello" class="com.kuang.controller.HelloController"/>

</beans>

(3). 写一个controller类来实现Controller接口

//注意:这里我们先导入Controller接口
public class HelloController implements Controller {

   public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
       //ModelAndView 模型和视图
       ModelAndView mv = new ModelAndView();

       //封装对象--存数据,放在ModelAndView中
       mv.addObject("msg","HelloSpringMVC!");
       //封装要跳转的视图,放在ModelAndView中
       //设置视图的名字
       mv.setViewName("hello");  //表示:自动拼拼接成这样子: /WEB-INF/jsp/hello.jsp
       return mv;
  }
}

(4). 编写一个简单的hello.jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>Title</title>
</head>
<body>

${msg}

</body>
</html>

(5). 结果展示:

image-20211112211900483

代码流程:当Tomcat发布成功,从浏览器地址栏输入请求"/hello",会跳到web.xml里的servlet-mapping去匹配,因为我们在那里设置了<url-pattern>/</url-pattern>表示除了.jsp的请求,其余的请求都能匹配到,然后根据servlet-name往上找到这个名字所绑定的类,由代码可以知道,这个类关联了spring的核心配置文件,即表示找到了这个类就能找到了spring的核心配置文件,接着“/hello”这个请求会通过diapatcherservlet转发到spring核心配置文件里并依次经过映射器、适配器,到达适配器之后,适配器会根据这个请求去匹配spring容器里包含的bean的id,看是否能匹配上,能匹配上的话,就会通过这个bean绑定的class跳转到相应的controller类,在这个类中会把相关数据(调用业务层所返回来的结果)和要去的页面名字封装进ModelAndView,然后通过return把ModelAndView返回到适配器,再由适配器转发到dispatcherservlet,接着再经dispatcherservlet转发到spring核心配置文件里的视图解析器,最后这个视图解析器会根据ModelAndView的信息自动拼接所要去往的页面路径,并将ModelAndView里所携带的数据显示到页面上!!

 

(5). 可能遇到的问题:

启动tomcat时,报404找不到页面!

解决方法:

  • 查看控制台输出,看一下是不是缺少了jar包

  • 如果jar包存在,但在out文件夹没有找到,就在IDEA的项目中添加lib文件夹,把项目所需要的所有jar包放进去

    image-20211107212710210

    image-20211107212905554

    image-20211107212947371

    image-20211107213034615

  • 重启Tomcat即可解决!

 

3.3、SpringMVC执行原理

image-20211107215734334

image-20211107220137081

image-20211107220207810

image-20211107220556739

先找处理器,再找适配器,接着去调用视图解释器,最后DispatcherServlet根据视图解析器的结果调用具体的视图,并将其呈现给用户!!

 

3.4、再建一个项目巩固学习

(1). 新建一个test.jsp页面

image-20211108154631915

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>Title</title>
</head>
<body>

${msg}

</body>
</html>

(2). 配置DispatchServlet

<?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.配置DispatchServlet:这个是SpringMvc的核心,请求分发器也叫前端控制器-->
   <!--所有的请求都会经过它-->
   <!--有servlet一定要配一个servletmapping,它们两个是成对出现的!!-->
   <servlet>
       <servlet-name>springmvc</servlet-name>
       <!--这个名字要对应一个类servlet类,在这里,这个类是spring固定的了!-->
       <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

       <!--2. DispatchServlet要绑定spring的配置文件-->
       <init-param>
           <!--通过下面的值定位到spring的配置文件并绑定-->
           <param-name>contextConfigLocation</param-name>
           <!--classpath:表示当前类的路径-->
           <param-value>classpath:springmvc-servlet.xml</param-value>
       </init-param>

       <!--3.设置启动级别为1,因为有些请求是当服务器一启动,它就请求了,所以
       将启动级别设置为1,让这个项目和服务器一起启动!!-->
       <load-on-startup>1</load-on-startup>

   </servlet>

   <!--
  在springmvc中:
  / :表示只匹配相关的请求,不会去匹配jsp页面
  /* : 表示匹配所有的请求,包括jsp页面
  注意:如果你用了/*,那么到后面走视图解释器拼接路径时,它就会变成hello.jsp.jsp……无限的嵌套下去
  (因为每个jsp的后缀,它都会进视图解释器,所以会导致无限循环下去)
   -->
   <servlet-mapping>
       <!--注册它的请求!!-->
       <servlet-name>springmvc</servlet-name>
       <url-pattern>/</url-pattern>
   </servlet-mapping>
   
</web-app>

(4). 编写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"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">

   <!--springmvc的核心三要素-->
   <!--4.处理器映射器-->
   <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

   <!--5.处理器适配器-->
   <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>

   <!--6.视图解释器:(以后会接触到模板引擎Thymeleaf、Freemarker……):绝对不可能省略-->
   <!--12.将controller返回的视图拼接上并把数据填充上去,然后交给DispatchServlet,最后用户就拿到数据了!-->
   <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
       <!--前缀:路径一般都是固定的-->
       <property name="prefix" value="/WEB-INF/jsp/"/>
       <!--后缀-->
       <property name="suffix" value=".jsp"/>
   </bean>

   <!--.BeanNameUrlHandlerMapping这个处理器需要根据bean的名字来进行处理-->
   <!--id表示我们要请求的位置,可由我们在浏览器地址栏那里输入!!
   然后把这个请求id交给class,即交给controller进行处理-->
   <bean id="/hello" class="com.kuang.controller.HelloController"/>

</beans>

(5). 编写controller类

package com.kuang.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

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

//7.实现Controller接口
public class HelloController implements Controller {
   public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

       //8.先new一个ModelAndView
       //这个类是返回一个ModelAndView交给视图解析器去执行
       ModelAndView mv = new ModelAndView();

       //9.业务代码(执行完会有返回值的!!)
       //假设现在就返回了字符串
       String result="HelloSpringMVC";
//     //这个是键值对,把我们的返回值封装到ModelAndView中
       mv.addObject("msg",result);

       //10.视图跳转 :直接设置视图的名字就可以了
       mv.setViewName("test");
       //11.就会把mv返回到视图解析器去处理
       return mv;

  }
}

 

4. 使用注解开发SpringMVC

4.1、代码示例

(1). 在web.xml中配置DispatchServlet

<?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.配置DispatchServlet:这个是SpringMvc的核心,请求分发器也叫前端控制器、前置控制器-->
   <!--所有的请求都会经过它-->
   <!--有servlet一定要配一个servletmapping,它们两个是成对出现的!!-->
   <servlet>
       <servlet-name>springmvc</servlet-name>
       <!--这个名字要对应一个servlet类,在这里,这个类是spring固定的了!-->
       <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

       <!--2. DispatchServlet要绑定spring的配置文件-->
       <init-param>
           <!--通过下面的值定位到spring的配置文件并绑定-->
           <param-name>contextConfigLocation</param-name>
           <!--classpath:表示当前类的路径-->
           <param-value>classpath:springmvc-servlet.xml</param-value>
       </init-param>

       <!--3.设置启动级别为1,因为有些请求是当服务器一启动,它就请求了,所以
       将启动级别设置为1,让这个项目和服务器一起启动!!-->
       <load-on-startup>1</load-on-startup>

   </servlet>

   <!--
  在springmvc中:
  / :表示只匹配所有的请求,不会去匹配jsp页面
  /* : 表示匹配所有的请求,包括jsp页面
  注意:如果你用了/*,那么到后面走视图解释器拼接路径时,它就会变成hello.jsp.jsp……无限的嵌套下去
  (因为每个jsp的后缀,它都会进视图解释器,所以会导致无限循环下去)
   -->
   <servlet-mapping>
       <!--注册它的请求!!-->
       <servlet-name>springmvc</servlet-name>
       <url-pattern>/</url-pattern>
   </servlet-mapping>
</web-app>

(2). 在springmvc-servlet.xml中配置支持注解驱动

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"
      xmlns:mvc="http://www.springframework.org/schema/mvc"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">

   <!--4. 自动扫描包,让指定包下的注解生效,由IOC容器统一管理-->
   <context:component-scan base-package="com.kuang.controller"/>

   <!--5. 让SpringMVC不处理静态资源;我们平时会去引用一些资源,比如:
   .css,.js,.html,.mp3,这时候我们不能让这些东西走视图解析器,所以我们
   要过滤,处理掉这些东西!!-->
   <mvc:default-servlet-handler/>

   <!--6.支持注解驱动:
         在spring中一般采用@RequestMapping注解来完成映射关系
         要想使@RequestMapping注解生效,就必须像上下文中注册DefaultAnnotationHandlerMapping
         和一个AnnotationMethodHandlerAdapter实例,这两个实例分别在类级别和
         方法级别处理,而加了annotaion-driven配置就会帮我们自动完成上述两个实例的注入!!-->
   <mvc:annotation-driven/>

   <!--7.视图解析器-->
   <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
       <property name="prefix" value="/WEB-INF/jsp/"/>
       <property name="suffix" value=".jsp"/>
   </bean>

</beans>

(3). 编写Controller类

@Controller、@RequestMapping("XXX")、Model model

@Controller
//7. 加这个注解,表明这个类已经被spring装配了
//在配置文件里就不用再配bean了
//@RequestMapping("/hello") 访问的父路径,下面的所有路径都要在这个基础上进行访问
//这些路径有一个父子关系
public class HelloController {

   //8.配置请求;多个请求就在这个类中加上多个方法并用注解配置请求就行了
   //不用再多写servlet
   @RequestMapping("/hello") //访问地址
   public String hello(Model model){

       //业务封装数据--通过model
       //9.向模型中添加属性msg与值,可以在jsp页面取出并渲染
       model.addAttribute("msg","Hello,SpringMVCAnnotation");


       return "hello"; //返回的这个字符串就是视图的名字,会被视图解析器处理
  }
}

 

4.2、Controller配置总结

(1). 控制器Controller

  • 控制器提供复杂的访问应用程序的行为,通常通过接口定义或注解定义这两种方式来实现

  • 控制器负责解析用户的请求并将其转换为一个模型

  • 在springMVC使用注解的情况下,一个控制器类可以包含多个方法

  • 在springMVC中,对于Controller的配置方式有很多种

(2). 编写一个controller类实现Controller接口

  • Controller是一个接口,在org.springframework.web.servlet.mvc包下,接口中只有一个方法ModelAndView handleRequest

  • 实现该接口的类就会获得控制器的功能:处理请求并返回一个模型与视图对象!

  • controller类写完,要去spring配置文件中注册请求的bean,name或id对应请求路径,class对应处理请求的类

    <bean id="/hello" class="com.kuang.controller.HelloController"/>
  • 缺点:一个控制器中只能写一个方法,如果要多个方法则需要定义多个controller类,这种定义方式比较麻烦!!

(3). 使用注解@Controller

  • @Controller注解用于声明spring类的实例是一个控制器

  • spring可以使用扫描机制来找到应用程序中所有基于该注解的控制器类,为了保证spring能找到你的控制器,一定要在配置文件中声明组件扫描!!

  • @Controller代表这个类会被spring接管,这个类中的所有方法,如果返回值是String,并且有具体的页面可以跳转,那么就会被视图解析器解析!!

 

4.3、@RequestMapping总结

  • @RequestMapping注解用于映射url到控制器类或一个特定的处理程序的方法

  • 既可以用于类上也可以用于方法上

  • 用于类上,表示类中所有相应请求的方法都要以该地址作为父路径才能进行跳转!!

  • 多个请求走同一个方法时:只需用value加花括号设置多个请求即可!!

    @RequestMapping(value={"/goHistoric","/goHistoric2"}

     

 

5. RestFul风格

5.1、介绍

(1). 概念:RestFul就是一个资源定位及资源操作的风格,不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简介、更有层次、更安全(不暴露变量名)、更易于实现缓存等机制。

(2). 功能

  • 资源:互联网所有的事务都可以被抽象为资源

  • 资源操作:使用POST、DELETE、PUT、GET,使用不同的请求方式对资源进行操作

  • 分别对应添加、删除、修改、查询

image-20211108192734016

传统的参数通过问号分隔且暴露变量名;RestFul通过/来分隔,不暴露变量名!!

(3). @PathVariable

image-20211108194742022

在浏览器地址栏中输入:local host:8080/commit/1/2,那么就会将参数传给p1,p2,将进行运算,将结果返回到jsp页面上!

image-20211108195626386

地址栏一样,但是设置的请求方式不同,所以会有不同jsp页面,可以达到url的复用。如果请求方式相同且地址栏相同,那么就会报以下错误:

image-20211108200807955

 

5.2、指定请求方式

(1). 方式一(使用method)

image-20211108200159110

(2). 方式二(使用注解)

image-20211108200330031

@RequestMapping是一个总的注解,无论是哪种请求方式都适用!!

 

6. 转发和重定向

(1). 没有视图解析器(原生态的)

image-20211108202812394

(2). 没有视图解析器2

image-20211108202939101

相当于用了原生的转发和重定向,只不过比原来代码的简洁而已!!

地址栏输入原来的请求

image-20211108203203971

重定向之后:

image-20211108203336428

(3). 有视图解析器

image-20211108203500293

 

7. 前端接收请求参数及数据回显的方式

7.1、接收请求参数的方式

(1). 传统的获得表单的数据

request.getParameters( )

(2). 处理前端提交的数据

  • 提交的域名称和处理方法的参数名一致

    地址栏输入:loacalhost:8080/user/t1?name=yuzhou

    @RequestMapping("/user")
    public class UserController {

       @GetMapping("/t1")
       public String test1(String name,Model model){
           //1.接收前端参数
           System.out.println("接收到前端的数据为"+name);

           //2.将返回的结果传递给前端,一般我们通过model
           model.addAttribute("msg",name);

           //3.跳转视图
           return "hello";
      }
    }

    控制台输出:接收到前端的数据为yuzhou

    页面输出:yuzhou

  • 提交的域名称和处理方法的参数名不一致

    @RequestParam:这个注解用不用都加上,可以清晰地让你知道这个参数是从前端接收的,方便你维护,而从前端传参只能按注解里的名字来传。这个注解限定可以避免很多无用的请求。比如上面那种方式,无论你在地址栏里输入什么,它都会传给name,这有可能造成无用的请求出现!!所以要养成这个习惯!!

    地址栏输入:loacalhost:8080/user/t1?username=zhangyuzhou

    @RequestMapping("/user")
    public class UserController {

       @GetMapping("/t1")
       public String test1(@RequestParam("username") String name, Model model){
           //@RequestParam:这个注解用不用都加上,可以清晰地让你知道这个参数是从前端接收的,方便你维护,而从前端传参只能按注解里的名字来传

           //1.接收前端参数
           System.out.println("接收到前端的数据为"+name);

           //2.将返回的结果传递给前端,一般我们通过model
           model.addAttribute("msg",name);

           //3.跳转视图
           return "hello";
      }
    }

    控制台输入:接收到前端的数据为zhangyuzhou

    页面显示:zhangyuzhou

  • 提交的是一个对象

    假设转递的是一个对象User并能匹配User对象(实体类)中的字段名,那么就传参成功,如果匹配不到就为null!!

    地址栏输入:loacalhost:8080/user/t1?id=1&name=yuzhou&age=21

    实体类:

    public class User {
       private int id;
       private String name;
       private int age;
    }
    @RequestMapping("/user")
    public class UserController {

       //前端提交的是一个对象(通过表单提交)
       @GetMapping("/t2")
       public String test2(User user){
           System.out.println(user);
           //3.视图跳转
           return "hello";
      }
    }

    控制台输入:User(id=1,name=zhangyuhzou,age=21)

    假设如果name不一致,那么就为null,结果如下:User(id=1,name=null,age=21)

 

7.2、数据返回到前端

页面

(1). 通过ModelAndView返回(最原始的)

(2). 通过Model(继承了ModelMap)返回---精简版的,大多数情况下我们会使用它!!

(3). 通过ModelMap(继承了LinkedHashMap)返回,用法和Model是一样的!!

@GetMapping("/t3")
   public String test3(@RequestParam("username")String name, ModelMap model){

       //封装要显示到视图中的数据,相当于req.setAttibute("name",name);
       model.addAttribute("msg",name);
       System.out.println(name);
   
       //3.视图跳转
       return "hello";
  }
}

(4). 三者的区别

  • Model:只有寥寥几个方法适合用于存储数据,简化了新手对于Model对象的操作和理解

  • ModelMap继承了LinkedMap,除了实现了自身的一些方法,同样继承了LinkedMap的方法和特性

  • ModelAndView可以在存储数据的同时,可以进行设置返回的逻辑视图,进行控制视图层的跳转!!

 

8. 乱码问题

8.1、用servlet来解决

request.setCharacterEncoding("utf-8");

@Controller
public class EncodingController {

   @PostMapping("/e/t1")
   public String test1(String name, Model model, HttpServletRequest request) throws UnsupportedEncodingException {  
       request.setCharacterEncoding("utf-8");
       //name是表单提交的数据
       model.addAttribute("msg",name);
       return "hello";
  }
}

8.2、改请求方式

有时候用Post请求会出现乱码,你把它改成用Get请求,有机会能把乱码问题解决!!

8.3、写过滤器解决乱码

自写一个类实现Filter接口

public class EncodingFilter implements Filter {
   public void init(FilterConfig filterConfig) throws ServletException {

  }

   public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
       
       request.setCharacterEncoding("utf-8");
       response.setCharacterEncoding("utf-8");
       //让这个链走下去,不要让它卡在这
       filterChain.doFilter(request,response);
  }

   public void destroy() {

  }
}

一定要去配置文件web.xml配置一下

<url-pattern>/*</url-pattern>

<!--配置过滤器-->
<filter>
   <filter-name>encoding</filter-name>
   <filter-class>com.kuang.Filter.EncodingFilter</filter-class>
</filter>
<filter-mapping>
   <filter-name>encoding</filter-name>
   <!--这里是要用/*,因为它的乱码有可能出现在jsp页面里,所以
       我们要拦截有关jsp页面的请求并处理它-->
   <url-pattern>/*</url-pattern>
</filter-mapping>

 

8.4、用SpringMVC提供的过滤器解决乱码

直接在web.xml配置就可以用了!!

<!--配置SpringMVC的过滤器-->
<filter>
   <filter-name>encoding</filter-name>
   <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
   <filter-name>encoding</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

 

8.5、检查Tomcat的配置文件

找到tomcat----->conf---->打开servlet.xml,做以下修改:

image-20211109114059281

 

8.6、网上大神写的自定义过滤器(终极方法了)

/**
* 解决get和post请求 全部乱码的过滤器
*/
public class GenericEncodingFilter implements Filter {

  @Override
  public void destroy() {
}

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
      //处理response的字符编码
      HttpServletResponse myResponse=(HttpServletResponse) response;
      myResponse.setContentType("text/html;charset=UTF-8");

      // 转型为与协议相关对象
      HttpServletRequest httpServletRequest = (HttpServletRequest) request;
      // 对request包装增强
      HttpServletRequest myrequest = new MyRequest(httpServletRequest);
      chain.doFilter(myrequest, response);
}

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

}

//自定义request对象,HttpServletRequest的包装类
class MyRequest extends HttpServletRequestWrapper {

  private HttpServletRequest request;
  //是否编码的标记
  private boolean hasEncode;
  //定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰
  public MyRequest(HttpServletRequest request) {
      super(request);// super必须写
      this.request = request;
}

  // 对需要增强方法 进行覆盖
  @Override
  public Map getParameterMap() {
      // 先获得请求方式
      String method = request.getMethod();
      if (method.equalsIgnoreCase("post")) {
          // post请求
          try {
              // 处理post乱码
              request.setCharacterEncoding("utf-8");
              return request.getParameterMap();
        } catch (UnsupportedEncodingException e) {
              e.printStackTrace();
        }
    } else if (method.equalsIgnoreCase("get")) {
          // get请求
          Map<String, String[]> parameterMap = request.getParameterMap();
          if (!hasEncode) { // 确保get手动编码逻辑只运行一次
              for (String parameterName : parameterMap.keySet()) {
                  String[] values = parameterMap.get(parameterName);
                  if (values != null) {
                      for (int i = 0; i < values.length; i++) {
                          try {
                              // 处理get乱码
                              values[i] = new String(values[i]
                                    .getBytes("ISO-8859-1"), "utf-8");
                        } catch (UnsupportedEncodingException e) {
                              e.printStackTrace();
                        }
                    }
                }
            }
              hasEncode = true;
        }
          return parameterMap;
    }
      return super.getParameterMap();
}

  //取一个值
  @Override
  public String getParameter(String name) {
      Map<String, String[]> parameterMap = getParameterMap();
      String[] values = parameterMap.get(name);
      if (values == null) {
          return null;
    }
      return values[0]; // 取回参数的第一个值
}

  //取所有值
  @Override
  public String[] getParameterValues(String name) {
      Map<String, String[]> parameterMap = getParameterMap();
      String[] values = parameterMap.get(name);
      return values;
}
}

去web.xml配置一下这个自定义类:

<!--配置过滤器-->
<filter>
   <filter-name>encoding</filter-name>
   <filter-class>com.kuang.Filter.GenericEncodingFilter</filter-class>
</filter>
<filter-mapping>
   <filter-name>encoding</filter-name>
   <!--这里是要用/*,因为它的乱码有可能出现在jsp页面里,所以
       我们要拦截有关jsp页面的请求并处理它-->
   <url-pattern>/*</url-pattern>
</filter-mapping>

一般我们直接用SpringMVC的过滤器就可以了

 

9. JSON

9.1、前后端分离时代

image-20211109121306273

9.2、什么是json?

  • JSON(JavaScriptObject Notation, JS 对象简谱) 是一种轻量级的数据交换格式,目前使用特别广泛!

  • 采用完全独立于编程语言的文本格式来存储和表示数据。

  • 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。

  • 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率(文件非常小,传输非常快!)。

在 JS 语言中,一切都是对象。因此,任何支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。但是对象和数组是比较特殊且常用的两种类型:

  • 对象表示为键值对,数据由逗号分隔

  • 花括号保存对象

  • 方括号保存数组

说白了。json就是一个字符串!!

JSON 键/值对

JSON 键值对是用来保存 JS 对象的一种方式,和 JS 对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 "" 包裹,使用冒号 : 分隔,然后紧接着值:

{"name":"yuzhou"}
{"age" :"3"}
{"sex":"男"}

 

JSON 与 JS 对象的关系

很多人搞不清楚 JSON 和 Js 对象的关系,甚至连谁是谁都不清楚。其实,可以这么理解:

JSON 是 JS 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。

var obj={a:'Hello',b:'World'}; //这是一个对象,注意键名也是可以用引号包裹的
var json=' {"a":"Hello","b":"World"} '; //这是一个json字符串,本质是一个字符串!!

JSON 和 JS 对象互转

要实现从对象转换为 JSON 字符串,使用 JSON.stringify() 方法:

它只是一个字符串,在浏览器控制台展开不了!!

var json=JSON.stringify({a:'Hello',b:'World'})
//结果是'{"a":"Hello","b":"World"}'

要实现从 JSON 转换为对象,使用 JSON.parse() 方法:

他是一个对象,能在浏览器控制台展开!!

var obj=JSON.parse('{"a":"Hello","b":"World"}');
//结果是{a:'Hello',b:'World'}

 

10. JSON解析工具

将数据不经过视图解析器传到前端供前端工作人员使用,从而实现前后端分离!!

10.1、Jackson

(1). 先导入它的依赖

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.13.0</version>
</dependency>

(2). 配置web.xml

<!--1.配置DispatchServlet:这个是SpringMvc的核心,请求分发器也叫前端控制器-->
<!--所有的请求都会经过它-->
<!--有servlet一定要配一个servletmapping,它们两个是成对出现的!!-->
<servlet>
   <servlet-name>springmvc</servlet-name>
   <!--这个名字要对应一个类servlet类,在这里,这个类是spring固定的了!-->
   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

   <!--2. DispatchServlet要绑定spring的配置文件-->
   <init-param>
       <!--通过下面的值定位到spring的配置文件并绑定-->
       <param-name>contextConfigLocation</param-name>
       <!--classpath:表示当前类的路径-->
       <param-value>classpath:springmvc-servlet.xml</param-value>
   </init-param>

   <!--3.设置启动级别为1,因为有些请求是当服务器一启动,它就请求了,所以
       将启动级别设置为1,让这个项目和服务器一起启动!!-->
   <load-on-startup>1</load-on-startup>

</servlet>

<!--
  在springmvc中:
  / :表示只匹配所有的请求,不会去匹配jsp页面
  /* : 表示匹配所有的请求,包括jsp页面
  注意:如果你用了/*,那么到后面走视图解释器拼接路径时,它就会变成hello.jsp.jsp……无限的嵌套下去
  (因为每个jsp的后缀,它都会进视图解释器,所以会导致无限循环下去)
   -->
<servlet-mapping>
   <!--注册它的请求!!-->
   <servlet-name>springmvc</servlet-name>
   <url-pattern>/</url-pattern>
</servlet-mapping>

<!--配置springmvc的过滤器-->
<filter>
   <filter-name>encoding</filter-name>
   <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
   <filter-name>encoding</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

(3). 编写springmvc-servlet.xml配置文件

下面5,6这两步可以删掉,上面扫描包时已经可以替代这两个作用了!!

<?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
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">

   <!--4. 自动扫描包,让指定包下的注解生效,由IOC容器统一管理-->
   <context:component-scan base-package="com.kuang.controller"/>

   <!--5. 让SpringMVC不处理静态资源;我们平时会去引用一些资源,比如:
   .css,.js,.html,.mp3,这时候我们不能让这些东西走视图解析器,所以我们
   要过滤,处理掉这些东西!!-->
   <mvc:default-servlet-handler/>

   <!--6.支持注解驱动:
         在spring中一般采用@Request Mapping注解来完成映射关系
         要想使@RequestMapping注解生效,就必须像上下文中注册DefaultAnnotationHandlerMapping
         和一个AnnotationMethodHandlerAdapter实例,这两个实例分别在类级别和
         方法级别处理,而加了annotaion-driven配置就会帮我们自动完成上述两个实例的注入!!-->
   <mvc:annotation-driven/>
   <!--5,6这两部可以删掉,上面扫描包时已经可以替代这两个作用了-->

   <!--7.视图解析器-->
   <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
       <property name="prefix" value="/WEB-INF/jsp/"/>
       <property name="suffix" value=".jsp"/>
   </bean>

</beans>

(4). 使用toString返回一个字符串(最原始的)

注意@Controller和@ResponseBody的搭配使用,我们返回一个json字符串给前端是不用走视图解析器的!!

@RestController的使用!!

@Controller //这个类会被视图解析器解析
//@RestController //表示这个类的所有方法都不会走视图解析器
public class UserController {

   @RequestMapping("/j1")
   @ResponseBody  //这个方法不会走视图解析器,直接返回一个字符串
   public String json1(){

       //创建一个对象
       User user = new User("yuzhou1号", 3, "男");

       return user.toString();
}
}

(5). 使用jackson返回json字符串

ObjectMapperwriteValueAsString方法!!

@Controller //这个类会被视图解析器解析
public class UserController {

   @RequestMapping("/j1")
   @ResponseBody  //这个方法不会走视图解析器,直接返回一个字符串
   public String json1() throws JsonProcessingException {

       //创建一个对象
       User user = new User("yuzhou1号", 3, "男");

       //利用jackson输出json格式的字符串
       ObjectMapper mapper = new ObjectMapper();
       String s = mapper.writeValueAsString(user);

       return s;
}
}

(6). 解决json字符串乱码

  1. 通过@RequestMapping的produces(指定响应体返回类型和编码)属性来实现

@RequestMapping(value="/j1",produces="application/json;charset=utf=8")
  1. springmvc-servlet.xml配置文件统一配置解决json乱码

上一种方法比较麻烦,如果项目中有许多请求则每一个都要添加,可以通过Spring配置统一指定,这样就不用每次都去处理了!

添加一段消息StringHttpMessageConverter转换配置:

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

(7). 传一个集合对象,看一下转成的json字符串是如何

@Controller //这个类会被视图解析器解析
//@RestController //表示这个类的所有方法都不会走视图解析器
public class UserController {

//@RequestMapping(value="/j2",produces="application/json;charset=utf-8")

   @RequestMapping("/j2")
   @ResponseBody  //这个方法不会走视图解析器,直接返回一个字符串
   public String json2() throws JsonProcessingException {

       //创建一个对象
       ArrayList<User> users = new ArrayList<User>();
       User user1 = new User("yuzhou1号", 3, "男");
       User user2 = new User("yuzhou2号", 3, "男");
       User user3= new User("yuzhou3号", 3, "男");
       User user4 = new User("yuzhou4号", 3, "男");

       users.add(user1);
       users.add(user2);
       users.add(user3);
       users.add(user4);

       //利用jackson输出json格式的字符串
       ObjectMapper mapper = new ObjectMapper();
       String s = mapper.writeValueAsString(users);
       //return user.toString();
       return s;
  }
}

结果显示:[ {"name":"yuzhou1号","age":3,"sex":"男"}, {"name":"yuzhou2号","age":3,"sex":"男"},

{"name":"yuzhou3号","age":3,"sex":"男"},

{"name":"yuzhou4号","age":3,"sex":"男"} ]

(8). 输出时间戳字符串

@RequestMapping("/j3")
@ResponseBody  //这个方法不会走视图解析器,直接返回一个字符串
public String json3() throws JsonProcessingException {

   ObjectMapper mapper = new ObjectMapper();
   Date date = new Date(); //时间戳

   return mapper.writeValueAsString(date);
}

(8). 用java转成人看得懂得时间

SimpleDateFormat和format方法!!

@RequestMapping("/j3")
@ResponseBody  //这个方法不会走视图解析器,直接返回一个字符串
public String json3() throws JsonProcessingException {

   ObjectMapper mapper = new ObjectMapper();
   Date date = new Date(); //时间戳

   SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

   return mapper.writeValueAsString(sdf.format(date));
}

结果显示:"2021-11-09 16:17:35"

(9). 使用ObjectMapper实现时间格式json字符串输出

@RequestMapping("/j4")
@ResponseBody  //这个方法不会走视图解析器,直接返回一个字符串
public String json4() throws JsonProcessingException {

   ObjectMapper mapper = new ObjectMapper();
   //ObjectMapper不使用时间戳的格式输出!!
   mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);
   //自定义时间格式
   SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
   
   //将mapper输出设置格式!!
   mapper.setDateFormat(sdf);
   Date date = new Date(); //时间戳

   return mapper.writeValueAsString(date);
}

(10). 综上所述,按源码的写法风格封装简单json工具类

这个工具类适用于输出对象、对象集合和输出时间!!

public class JsonUtils {

   public static String getJson(Object object){
       //源码代码的写法,重载时不会再写重复的代码了
       //后面的那个格式不能为null
       return getJson(object,"yyyy-MM-dd HH:mm:ss");
  }
   public static String getJson(Object object,String dateFormat) {
       ObjectMapper mapper = new ObjectMapper();
       mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);
       SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
       mapper.setDateFormat(sdf);
       try {
           return mapper.writeValueAsString(object);
      } catch (JsonProcessingException e) {
           e.printStackTrace();
      }
       return null;
  }
}

 

10.2、FastJson

fastjson.jar是阿里开发的一款专门用于Java开发的包,可以方便的实现json对象与JavaBean对象的转换,实现JavaBean对象与json字符串的转换,实现json对象与json字符串的转换。实现json的转换方法很多,最后的实现结果都是一样的。

(1). 导入依赖

<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>1.2.78</version>
</dependency>

(2). controller类使用阿里巴巴的fastjson来进行转换

//fastJson
@RequestMapping("/j6")
@ResponseBody  //这个方法不会走视图解析器,直接返回一个字符串
public String json6() throws JsonProcessingException {

   //创建一个对象
   ArrayList<User> users = new ArrayList<User>();
   User user1 = new User("yuzhou1号", 3, "男");
   User user2 = new User("yuzhou2号", 3, "男");
   User user3= new User("yuzhou3号", 3, "男");
   User user4 = new User("yuzhou4号", 3, "男");

   users.add(user1);
   users.add(user2);
   users.add(user3);
   users.add(user4);

   System.out.println("*******Java对象 转 JSON字符串*******");
   //集合
   String str1 = JSON.toJSONString(users);
   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);

   return "test";
}

结果显示:

***Java对象 转 JSON字符串*** JSON.toJSONString(list)==>[ {"age":3,"name":"yuzhou1号","sex":"男"},{"age":3,"name":"yuzhou2号","sex":"男"},{"age":3,"name":"yuzhou3号","sex":"男"},

{"age":3,"name":"yuzhou4号","sex":"男"} ] JSON.toJSONString(user1)==>{"age":3,"name":"yuzhou1号","sex":"男"}

** JSON字符串 转 Java对象*** JSON.parseObject(str2,User.class)==>User(name=yuzhou1号, age=3, sex=男)

** Java对象 转 JSON对象 ** (JSONObject) JSON.toJSON(user2)==>yuzhou2号

** JSON对象 转 Java对象 ** JSON.toJavaObject(jsonObject1, User.class)==>User(name=yuzhou2号, age=3, sex=男)

页面显示:test

(3). 小结:

  • Java对象 转 JSON字符串:JSON.toJSONString(users)

  • JSON字符串 转 Java对象:JSON.parseObject(str2,User.class)

  • Java对象 转 JSON对象: JSONObject jsonObject1 = (JSONObject) JSON.toJSON(user2)、jsonObject1.getString("name")

  • JSON对象 转 Java对象:JSON.toJavaObject(jsonObject1, User.class)

(4). 有可能遇到的报错:

image-20211109181500144

 

11. SSM框架的整合

整个项目设计开发的简单路线:

image-20211109185520824

11.1、Mybatis层

(1). 创建一个存放书籍数据的数据库表

CREATE DATABASE `ssmbuild`;

USE `ssmbuild`;

DROP TABLE IF EXISTS `books`;

CREATE TABLE `books` (
`bookID` INT(10) NOT NULL AUTO_INCREMENT COMMENT '书id',
`bookName` VARCHAR(100) NOT NULL COMMENT '书名',
`bookCounts` INT(11) NOT NULL COMMENT '数量',
`detail` VARCHAR(200) NOT NULL COMMENT '描述',
KEY `bookID` (`bookID`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT  INTO `books`(`bookID`,`bookName`,`bookCounts`,`detail`)VALUES
(1,'Java',1,'从入门到放弃'),
(2,'MySQL',10,'从删库到跑路'),
(3,'Linux',5,'从进门到进牢');

(2). 新建一个maven项目,导入依赖并写上静态资源导出

    <!--导入依赖:junit,数据库驱动,连接池,servlet,jsp,
   mybatis,mybatis-spring,spring,lombok-->
   <dependencies>
       <dependency>
           <groupId>junit</groupId>
           <artifactId>junit</artifactId>
           <version>4.11</version>
       </dependency>
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>5.1.47</version>
       </dependency>
       <!--数据库连接池-->
       <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
       <dependency>
           <groupId>com.mchange</groupId>
           <artifactId>c3p0</artifactId>
           <version>0.9.5.5</version>
       </dependency>
       <dependency>
           <groupId>javax.servlet</groupId>
           <artifactId>javax.servlet-api</artifactId>
           <version>4.0.1</version>
       </dependency>
       <dependency>
           <groupId>javax.servlet.jsp</groupId>
           <artifactId>javax.servlet.jsp-api</artifactId>
           <version>2.3.3</version>
       </dependency>
       <dependency>
           <groupId>javax.servlet</groupId>
           <artifactId>jstl</artifactId>
           <version>1.2</version>
       </dependency>
       
       <dependency>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis</artifactId>
           <version>3.5.7</version>
       </dependency>

       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-webmvc</artifactId>
           <version>5.3.12</version>
       </dependency>
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-jdbc</artifactId>
           <version>5.3.12</version>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>1.18.22</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>
</project>

(3). 在idea中连接上数据库

image-20211109191918823

image-20211109192117185

image-20211109192332796

image-20211109192506177

(4). 建立包结构

image-20211109192738980

 

(5). 在Resource文件建立mybatis核心配置文件和spring的核心配置文件(头文件)

mybatis-config.xml(头文件):

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

</configuration>

applicationContext.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"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

(6). 在Resources文件夹下编写数据库配置文件database.properties:

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

注意:如果使用MySQL8.0+的版本的话,需要做以下操作(二选一)才不会报错:

  • 设置驱动:

    jdbc.driver=com.mysql.cj.jdbc.Driver
  • 增加一个时区的配置

    &serverTimezone=Aisa/Shanghai(定位到亚洲上海)

    jdbc.url=jdbc:mysql://localhost:3306/ssmbuild?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Aisa/Shanghai

(7). 编写mybatis-config.xml文件:

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

   <!--本来是要配置数据源的,但用spring之后这个交给spring来做了-->

   <!--允许这个包下的类起别名,什么别名都能使用,不用写全限定名了!!-->
   <typeAliases>
       <package name="com.kuang.pojo"/>
   </typeAliases>

   <!--注册mapper-->

</configuration>

(8). 在pojo包下建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Books {
   private int bookId;
   private String bookName;
   private int bookCounts;
   private String detail;
}

(9). 在mapper包下建接口让其对实体类进行操作

public interface BookMapper {

   //增加一本数
   int addBook(Books books);

   //删除一本书
   int deleteBookByID(@Param("bookID") int id);

   //更新一本书
   int updateBook(Books books);

   //查询一本书
   Books queryBookByID(@Param("bookID") int id);

   //查询全部的书
   List<Books> queryAllBook();
}

建立相应的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.kuang.mapper.BookMapper">

   <insert id="addBook" parameterType="Books">
      insert into ssmbuild.books(bookName, bookCounts, detail)
      VALUES(#{bookName},#{bookCounts},#{detail});
   </insert>

   <delete id="deleteBookById" parameterType="int">
      delete from ssmbuild.books where bookID=#{bookID}
   </delete>

   <update id="updateBook" parameterType="Books">
      update ssmbuild.books
      set bookName =#{bookName},bookCounts=#{bookCounts} ,detail=#{detail}
      where bookID=#{bookID};
   </update>
   
   <select id="queryBookById" resultType="Books">
      select * from ssmbuild.books where bookID=#{bookID};
   </select>

   <select id="queryAllBook" resultType="Books">
      select * from ssmbuild.books;
   </select>
</mapper>

在idea中写sql代码时,为了有数据库表的字段名提示,可以做以下设置:File----->setting----->Languages & Frameworks------>SQL Dialects----->点击右边栏的 + 号,选中我们的项目把其加进去,最后点ok即可!!

(10). 写完一个Mapper.xml先去mybatis的核心配置文件进行注册

<!--注册mapper-->
<mappers>
   <mapper class="com.kuang.mapper.BookMapper"/>
</mappers>

(11). 在service包下编写业务层(基本和mapper包下的一样)

BookService接口:

public interface BookService {

   //增加一本数
   int addBook(Books books);

   //删除一本书
   int deleteBookByID(int id);

   //更新一本书
   int updateBook(Books books);

   //查询一本书
   Books queryBookByID(int id);

   //查询全部的书
   List<Books> queryAllBook();
}

BookServiceImpl实现类:

业务层调mapper层,组合mapper层,加入set方法方便spring托管!!

public class BookServiceImpl implements BookService{

   //service层调dao层,组合dao层
   private BookMapper bookMapper;

   //有了set方法,到时候spring就可以托管它了!!
   public void setBookMapper(BookMapper bookMapper) {
       this.bookMapper = bookMapper;
  }

   public int addBook(Books books) {
       return bookMapper.addBook(books);
  }

   public int deleteBookByID(int id) {
       return bookMapper.deleteBookByID(id);
  }

   public int updateBook(Books books) {
       return bookMapper.updateBook(books);
  }

   public Books queryBookByID(int id) {
       return bookMapper.queryBookByID(id);
  }

   public List<Books> queryAllBook() {
       return bookMapper.queryAllBook();
  }
}

 

11.2、Spring层

(1). 整合mapper层,编写spring-dao.xml配置文件(IOC)

三个新知识点:

  • 关联数据库配置文件的操作

  • 使用c3p0连接池

  • 配置接口扫描包,动态的实现了dao接口可以注入到spring容器中

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

   <!--1.关联数据库配置文件-->
   <!--这个是spring专门用来读数据库配置文件的-->
   <context:property-placeholder location="classpath:database.properties"/>

   <!--2.连接池
           dbcp:半自动化操作,不能自动连接
           c3p0:自动化操作(自动化加载配置文件,并且可以自动设置到对象中)
           druid,hikari(这两个是公司常用的,以后再自行去了解吧)
           现在我们就不用spring的连接池了,我们用一下c3p0的
           -->
   <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}"/>

       <!-- c3p0连接池的私有属性 -->
       <property name="maxPoolSize" value="30"/>
       <property name="minPoolSize" value="10"/>
       <!-- 关闭连接后不自动commit -->
       <property name="autoCommitOnClose" value="false"/>
       <!-- 获取连接超时时间 -->
       <property name="checkoutTimeout" value="10000"/>
       <!-- 当获取连接失败重试次数 -->
       <property name="acquireRetryAttempts" value="2"/>

   </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>

   <!--之前我们需要再到层写一个实现来继承某个方法,才能实现在spring容器中
       注入类,现在我们来做一个简单的操作就行了-->
   <!--4.配置接口扫描包,动态的实现了dao接口可以注入到spring容器中-->
   <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
       <!--需要将sqlSessionfactory注入进去-->
       <!--value就是我们上面那个bean的名字-->
       <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>

       <!--要扫描的dao包-->
       <property name="basePackage" value="com.kuang.mapper"/>
   </bean>
</beans>

(2). 整合service层,编写spring-service.xml(AOP)

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

   <!--1.扫描service下的包-->
   <context:component-scan base-package="com.kuang.service"/>

   <!--2.将我们所有的业务类注入到spring容器中,可以通过配置或注解实现-->
   <bean id="BookServiceImpl" class="com.kuang.service.BookServiceImpl">
       <!--ref能引用别的spring的配置文件,是因为我们点spring文件顶上的黄色文字,让idea
       帮我们把spring的相关文件放到一起了,如果不这样,那只能通过import来手动导入spring配置文件了!-->
       <property name="bookMapper" ref="bookMapper"/>

   </bean>

   <!--3.声明式事务配置-->
   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <!--注入数据源-->
       <property name="dataSource" ref="dataSource"/>
   </bean>

   <!--4.aop事务支持:我们这个项目用不到,所以就不写了,你们
   了解步骤有他就行!!-->
</beans>

 

11.3、SpringMVC层

(1). 把项目变成web项目

右击你的项目名称------->Add Framework Support----->勾选web Application-------->点击ok,在你的项目中多了一个web的包即可!!

(2). 在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.配置DispatchServlet-->
   <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:applicationContext.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>

   <!--2.乱码过滤-->
   <filter>
       <filter-name>encodingFilter</filter-name>
       <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
       <init-param>
           <param-name>encoding</param-name>
           <param-value>utf-8</param-value>
       </init-param>
   </filter>
   <filter-mapping>
       <filter-name>encodingFilter</filter-name>
       <url-pattern>/*</url-pattern>
   </filter-mapping>

   <!--3.设置session-->
   <session-config>
       <session-timeout>15</session-timeout>
   </session-config>

</web-app>

(3). 在resources文件夹编写spring-mvc.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:mvc="http://www.springframework.org/schema/mvc"
      xmlns:comtext="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/mvc
      http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

   <!--1.注解驱动-->
   <mvc:annotation-driven/>
   <!--2.静态资源过滤-->
   <mvc:default-servlet-handler/>
   <!--3.扫描controller包-->
   <comtext:component-scan base-package="com.kuang.controller"/>

   <!--4.视图解析器-->
   <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
       <property name="prefix" value="/WEB-INF/jsp/"/>
       <property name="suffix" value=".jsp"/>

   </bean>
</beans>

(4). 去WEB-INF目录下建立起jsp包!

 

11.4、业务一:查询全部书籍展示

(1). 在controller包下编写相应controller类

@Controller
@RequestMapping("/book")
public class BookController {
   //controller层调service层,并和jsp页面进行交互!!

   @Autowired
   @Qualifier("BookServiceImpl") //将该类注入容器
   private BookService bookService;

   //查询全部的书籍,并且返回到一个书籍展示页面
   @RequestMapping("/allBook")
   public String list(Model model){
       List<Books> list=bookService.queryAllBook();
       model.addAttribute("list",list);
       //查询全部的书籍信息并返回到相应的页面
       return "allBook";
  }
}

注意: List<Books> list=bookService.queryAllBook();这个方法必须从mapper开始就设置它的返回值为list集合,不然在页面用c标签遍历查询出来的结果时会报错:“不知道如何遍历结果”!!一定要记住如果要在页面用c标签输出后台数据的话,一开始的时候就将这个方法的返回值设置为集合。这是我目前的一个粗浅的理解!!

(2). 编写allBook.jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>书籍展示</title>
</head>
<body>
<h1></h1>
</body>
</html>

(3). 编写index.jsp页面,从首页跳到allBook.jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>书籍展示</title>
</head>
<body>
<h3>
          <%--取绝对地址,保证项目发布时路径也不出错!!--%>
           <a href="${pageContext.request.contextPath}/book/allBook">进入书籍页面</a>
   </h3>
</body>
</html>

(5). 美化index页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
   <head>
       <title>首页</title>

       <style>
           a{
               text-decoration: none;
               color:black;
               font-size:18px;
          }
           h3{
               width:180px;
               height:38px;
               margin:100px auto;
               text-align:center;
               line-height:38px;
               background:deepskyblue;
          }
       </style>
   </head>
   <body>
       <h3>
          <%--取绝对地址,保证项目发布时路径也不出错!!--%>
           <a href="${pageContext.request.contextPath}/book/allBook">进入书籍页面</a>
       </h3>
   </body>
</html>

样式效果:

image-20211110084937848

(6). 使用BootStrap来美化allBook页面并用c标签完成数据的导出

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<c:forEach var="book" items="${list}"> <%--<c:forEach var="book" items="${requestScope.get('list')}">--%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>书籍展示</title>

  <%--使用BootStrap美化界面--%>
   <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

<div class="container">
   <div class="row clearfix">
       <div class="col-md-12">
           <div class="page-header">
               <h1>
                   <small>书籍列表 ———— 显示所有书籍</small>
               </h1>
           </div>
       </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>
                   </tr>
               </thead>

              <%--书籍从数据库中查询出来,从这个list中遍历出来,用foreach--%>
               <tbody>
                   <c:forEach var="book" items="${list}">
                  <%--<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>
                       </tr>

                   </c:forEach>

               </tbody>
           </table>
       </div>
   </div>
</div>
</body>
</html>

页面展示:

image-20211110162228773

 

11.5、业务二:增加书籍信息

(1). 在allBook.jsp页面加一个链接跳转到新写的toAddBook请求(controller层)

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>书籍展示</title>

  <%--使用BootStrap美化界面--%>
   <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

<div class="container">
   <div class="row clearfix">
       <div class="col-md-12">
           <div class="page-header">
               <h1>
                   <small>书籍列表 ———— 显示所有书籍</small>
               </h1>
           </div>
       </div>

       <div class="row">
           
           <div  class="col-md-4 column">
              <%--这里的链接是走请求的(到contoller层),不是走页面的--%>
               <a class="btn btn-primary" href="${pageContext.request.contextPath}/book/toAddBook">新增书籍</a>
               
           </div>
       </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>
                   </tr>
               </thead>

              <%--书籍从数据库中查询出来,从这个list中遍历出来,用foreach--%>
               <tbody>
                   <c:forEach var="book" items="${list}">
                  <%--<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>
                       </tr>

                   </c:forEach>

               </tbody>
           </table>
       </div>
   </div>
</div>
</body>
</html>

页面显示:

image-20211110165740265

(2). 在controller层写一个跳转页面的请求

//跳转到增加书籍的页面
@RequestMapping("/toAddBook")
public String toAddPaper(){

   return "addBook";
}
//添加书籍的请求
@RequestMapping("/addBook")
public String addBook(Books books){

   bookService.addBook(books);
   return "redirect:/book/allBook";
   //请求复用,不返回页面,因为你改了数据,要再查询数据库,然后再
   // 把数据输出来,这才是真正的业务
}

(3). 根据请求去到我们新写的addBook.jsp页面

注意点:

  • <%--点击添加之后,表单信息就会被提交到action这个请求里进行处理!然后根据这个请求回到controller层进行相应的处理和页面跳转!!--%>

  • <%--name="bookName":下面name的名字必须要和实体类的属性名一致,不然数据插不进去!! required:在表单里加了它,必须填进值才能提交,如果空值就会提示!!--%>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>新增书籍</title>
  <%--使用BootStrap美化界面--%>
   <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
   <div class="row clearfix">
       <div class="col-md-12">
           <div class="page-header">
               <h1>
                   <small>新增书籍</small>
               </h1>
           </div>
       </div>
   </div>

  <%--点击添加之后,表单信息就会被提交导action这个请求里进行处理!--%>
   <form action="${pageContext.request.contextPath}/book/addBook" method="post">
       <div class="form-group">

          <%--name="bookName":下面name的名字必须要和实体类的一致,不然数据插不进去!!
                required:在表单里加了它,必须填进值才能提交,如果空值就会提示!!--%>

           <label>书籍名称</label>
           <input type="text" name="bookName" class="form-control" required>
           <label>书籍数量</label>
           <input type="text" name="bookCounts" class="form-control" required>
           <label>书籍描述</label>
           <input type="text" name="detail" class="form-control" required>
       </div>
       <div class="form-group">
           <input type="submit" class="form-control" value="添加">
       </div>
   </form>
</div>
</body>
</html>

 

(4). 报错问题:

  1. bean找不到

image-20211110081346645

排错思路:

  • 查看这个bean注入是否成功!

    • 去到spring-service.xml看有没有id="BookServiceImpl"的bean并能在相应的controller点击绿色箭头能跳过来,就表示已经注入成功!!

  • 用Junit单元测试,看我们的代码是否能查询出来结果

    @Test
    public void test(){
       //导入这个文件才能获取容器中的所有bean,因为已经在这个文件将所有的springxml整合了!!
       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       BookService bookServiceImpl = context.getBean("BookServiceImpl", BookService.class);
       for (Books books : bookServiceImpl.queryAllBook()) {
           System.out.println(books);
      }
    }

    发现能查出结果来,则这一步也没问题!!

  • 得出初步结论:问题一定不在我们的底层,是spring出了问题!!

  • springmvc整合的时候没调用到我们的service层的bean

    • 1.applicationContext.xml中没有注入bean(这不可能,因为全部springxml已经导入进去了)

    • 2.在web.xml中,我们也绑定过配置文件,看我们是否绑错了,我们要绑总的配置文件!(最终问题出现的地方:我们绑错xml文件了,我们配置的是spring-mvc.xml,这里面确实没有service层的bean,所以报空指针!!)

      image-20211110083627370

 

  1. 未能加载或实例化TagLibraryValidator类(和jstl标签有关的)

image-20211110110008001

原因:是我先前倒错jstl的包了!!先前我导了这样的依赖:

<!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl -->
<dependency>
   <groupId>javax.servlet.jsp.jstl</groupId>
   <artifactId>jstl</artifactId>
   <version>1.2</version>
</dependency>

而后面更改为以下依赖就可以正确执行了:

<dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
   <version>1.2</version>
</dependency>

 

11.6、业务三:修改书籍和删除书籍

  1. 修改书籍信息

    (1). 在allBook.jsp页面增加一个修改链接:

    <td>
      <%--用传统方式传参过去--%>
       <a href="${pageContext.request.contextPath}/book/toUpdate?id=${book.getBookId()}">修改</a>
      <%--&nbsp; 代表将空格打印出来--%>
       &nbsp; | &nbsp;
       <a href="#">删除</a>
    </td>

    (2). 在controller中接收请求

    //跳转到修改页面
    @RequestMapping("/toUpdate")
    public String toUpdatePaper(int id,Model model){

       Books books = bookService.queryBookByID(id);

       model.addAttribute("QBook",books);
       return "updateBook";
    }

    (3). 编写updateBook.jsp页面

    注意点:

    • <%--问题解决:因为修改要用到id,而我们可以用隐藏域来传递, 这样前端就看不到我们的数据了,不会影响前端的设计!!--%>

    • 这个错,我虽然在前面写了,可是我自己在写这个页面时还是出现这个错,所以想再提醒一下你们!!

      <%--name="bookName":下面name的名字必须要和实体类的一致,不然数据插不进去!! required:在表单里加了它,必须填进值才能提交,如果空值就会提示!!--%>

    • %--value="${QBook.booName}":获取从model传过来的,并用value设置成默认值--%

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
       <title>修改书籍</title>
      <%--使用BootStrap美化界面--%>
       <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    </head>
    <body>
    <div class="container">
       <div class="row clearfix">
           <div class="col-md-12">
               <div class="page-header">
                   <h1>
                       <small>修改书籍</small>
                   </h1>
               </div>
           </div>
       </div>

      <%--点击添加之后,表单信息就会被提交到action这个请求里进行处理!--%>
       <form action="${pageContext.request.contextPath}/book/updateBook" method="post">

          <%--问题解决:因为修改要用到id,而我们可以用隐藏域来传递,
           这样前端就看不到我们的数据了,不会影响前端的设计!!--%>

           <input type="hidden" name="bookId" value="${QBook.bookId}">
           <div class="form-group">

              <%--name="bookName":下面name的名字必须要和实体类的一致,不然数据插不进去!!
                    required:在表单里加了它,必须填进值才能提交,如果空值就会提示!!--%>

               <label>书籍名称</label>
                  <%--value="${QBook.booName}":获取从model传过来的,并用value设置成默认值--%>
               <input type="text" name="bookName" class="form-control" value="${QBook.bookName}" required>
               <label>书籍数量</label>
               <input type="text" name="bookCounts" class="form-control" value="${QBook.bookCounts}" required>
               <label>书籍描述</label>
               <input type="text" name="detail" class="form-control" value="${QBook.detail}" required>
           </div>
           <div class="form-group">
               <input type="submit" class="form-control" value="修改">
           </div>
       </form>
    </div>
    </body>
    </html>
  2. 删除书籍

    (1). 在allBook.jsp页面增加一个删除链接

    <td>
      <%--用传统方式传参过去--%>
       <a href="${pageContext.request.contextPath}/book/toUpdate?id=${book.getBookId()}">修改</a>
      <%--&nbsp; 代表将空格打印出来--%>
       &nbsp; | &nbsp;
       <a href="${pageContext.request.contextPath}/book/deleteBook/${book.getBookId()}">删除</a>
    </td>

    (2). 在controller中接收请求

    //删除书籍restful风格
    @RequestMapping("/deleteBook/{bookId}")
    public String deleteBook(@PathVariable("bookId") int id){
       bookService.deleteBookByID(id);
       return "redirect:/book/allBook";
    }

注意:到此,这个项目的增删改查就告一段落了!令我最头疼的就是我犯了一个很低级的错误:就写Mapper时,以后那里的id一定要复制过去,千万不要、不要、不要自己手写,很容器大小写写错,导致最后找错找到吐!!

 

11.7、增加搜索功能

(1). 在dao包加入接口和配置

//根据名字搜索书籍
Books queryBookByName(@Param("bookName") String bookName);
<select id="queryBookByName" resultType="Books">
  select * from ssmbuild.books where bookName=#{bookName}
</select>

(2). 在service包加入接口和调用dao层方法

//根据名字搜索书籍
Books queryBookByName(String bookName);
public Books queryBookByName(String bookName) {
return bookMapper.queryBookByName(bookName);
}

(3). 在controller包加入请求跳转

//根据书籍名字查询
@RequestMapping("/queryBook")
public String queryBook(String queryBookName,Model model){

   Books books = bookService.queryBookByName(queryBookName);
   List<Books> list = new ArrayList<Books>();
   list.add(books);
   //如果查询不到,那么就返回所有数据
   if(books==null){

       list=bookService.queryAllBook();
       model.addAttribute("error","未查到!");
  }
   model.addAttribute("list",list);
   return "allBook";
}

(4). 在allBook.jsp页面加入新业务

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>书籍展示</title>

  <%--使用BootStrap美化界面--%>
   <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

<div class="container">
   <div class="row clearfix">
       <div class="col-md-12">
           <div class="page-header">
               <h1>
                   <small>书籍列表 ———— 显示所有书籍</small>
               </h1>
           </div>
       </div>

       <div class="row">
           <div  class="col-md-4 column">
               
               
               
              <%--这里的链接是走请求的(活到contoller层),不是走页面的--%>
               <a class="btn btn-primary" href="${pageContext.request.contextPath}/book/toAddBook">新增书籍</a>
                   <a class="btn btn-primary" href="${pageContext.request.contextPath}/book/allBook">显示全部书籍</a>
           </div>
           <div  class="col-md-4 column"></div>
           <div  class="col-md-4 column">
               
               
               
              <%--查询书籍--%>
               <form class="form-inline" action="${pageContext.request.contextPath}/book/queryBook" method="post" style="float:right">
                   <span style="color:red;font-weight:bold">${error}</span>
                   <input type="text" name="queryBookName" class="form-control"placeholder="请输入要查询的书籍名称">
                   <input type="submit" value="查询" class="btn btn-primary">
               </form>
               
               
               
           </div>
       </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>

              <%--书籍从数据库中查询出来,从这个list中遍历出来,用foreach--%>
               <tbody>
                   <c:forEach var="book" items="${list}">
                  <%--<c:forEach var="book" items="${requestScope.get('list')}">--%>
                       <tr>
                          <%--<td>${book.bookId}</td>--%>
                           <td>${book.getBookId()}</td>
                           <td>${book.getBookName()}</td>
                           <td>${book.getBookCounts()}</td>
                           <td>${book.getDetail()}</td>
                           <td>
                              <%--用传统方式传参过去--%>
                               <a href="${pageContext.request.contextPath}/book/toUpdate?id=${book.getBookId()}">修改</a>
                              <%--&nbsp; 代表将空格打印出来--%>
                                   &nbsp; | &nbsp;
                               <a href="${pageContext.request.contextPath}/book/deleteBook/${book.getBookId()}">删除</a>
                           </td>
                       </tr>

                   </c:forEach>

               </tbody>
           </table>
       </div>
   </div>
</div>
</body>
</html>

 

12. Ajax

12.1、简介

  • AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。

  • AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。

  • Ajax 不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的Web应用程序的技术。

  • 在 2005 年,Google 通过其 Google Suggest 使 AJAX 变得流行起来。Google Suggest能够自动帮你完成搜索单词。

  • Google Suggest 使用 AJAX 创造出动态性极强的 web 界面:当您在谷歌的搜索框输入关键字时,JavaScript 会把这些字符发送到服务器,然后服务器会返回一个搜索建议的列表。

  • 就和国内百度的搜索框一样!

  • 传统的网页(即不用ajax技术的网页),想要更新内容或者提交一个表单,都需要重新加载整个网页。

  • 使用ajax技术的网页,通过在后台服务器进行少量的数据交换,就可以实现异步局部更新

  • 使用Ajax,用户可以创建接近本地桌面应用的直接、高可用、更丰富、更动态的Web用户界面。

 

12.2、使用iframe标签体验页面无刷新的伪ajax

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>iframe体验页面无刷新</title>

   <script>
       function go(){
          var url= document.getElementById("url").value;
          document.getElementById("iframe1").src=url;
      }
   </script>
</head>
<body>

<div>
   <p>请输入地址</p>
   <p>
       <input type="text" id="url" value="https://www.baidu.com/?tn=88093251_34_hao_pg">
       <input type="button" value="提交" onclick="go()">
   </p>
</div>

<div>
   <iframe id="iframe1" style="width:100%;height:500px"></iframe>
</div>

</body>
</html>

页面显示:

image-20211111082035535

12.3、Ajax的核心

  • 纯JS原生实现Ajax我们不去讲解这里,直接使用jquery提供的,方便学习和使用,避免重复造轮子,有兴趣的同学可以去了解下JS原生XMLHttpRequest !

  • Ajax的核心是XMLHttpRequest对象(XHR)。XHR为向服务器发送请求和解析服务器响应提供了接口。能够以异步方式从服务器获取新数据。

  • jQuery 提供多个与 AJAX 有关的方法。

  • 通过 jQuery AJAX 方法,您能够使用 HTTP Get 和 HTTP Post 从远程服务器上请求文本、HTML、XML 或 JSON – 同时您能够把这些外部数据直接载入网页的被选元素中。

  • jQuery 不是生产者,而是大自然搬运工。

  • jQuery Ajax本质就是 XMLHttpRequest对他进行了封装,方便调用!

  • 部分参数:

    掌握三个东西:

    • url:请求地址

    • data:要发送的数据

    • success:成功之后执行的回调函数(全局) error:失败之后执行的回调函数(全局)

    jQuery.ajax(...)
        部分参数:
              url:请求地址
              type:请求方式,GET、POST(1.9.0之后用method)
          headers:请求头
              data:要发送的数据
      contentType:即将发送信息至服务器的内容编码类型(默认: "application/x-www-form-urlencoded; charset=UTF-8")
            async:是否异步
          timeout:设置请求超时时间(毫秒)
        beforeSend:发送请求前执行的函数(全局)
          complete:完成之后执行的回调函数(全局)
          success:成功之后执行的回调函数(全局)
            error:失败之后执行的回调函数(全局)
          accepts:通过请求头发送给服务器,告诉服务器当前客户端可接受的数据类型
          dataType:将服务器端返回的数据转换成指定类型
            "xml": 将服务器端返回的内容转换成xml格式
            "text": 将服务器端返回的内容转换成普通文本格式
            "html": 将服务器端返回的内容转换成普通文本格式,在插入DOM中时,如果包含JavaScript标签,则会尝试去执行。
          "script": 尝试将返回值当作JavaScript去执行,然后再将服务器端返回的内容转换成普通文本格式
            "json": 将服务器端返回的内容转换成相应的JavaScript对象
          "jsonp": JSONP 格式使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数

(2). 在web导入jquery-3.6.0.js静态资源文件

这里我就不用cdn了,直接导入文件到web项目中即可!没有这个文件的,可以搜索“jQuery”去官网下载即可!!

  1. 在web文件夹下建一个static包

    image-20211111083109579

  2. 在jsp或html页面引入js文件

    <head>
       <title>$Title$</title>

      <%--引入js文件(jquery)--%>
       <script src="${pageContext.request.contextPath}/static/js/jquery-3.6.0.js"></script>
    </head>
    <body>
  3. 验证是否成功导入

    image-20211111084432497

 

12.4、模仿百度失去焦点就能产生请求事件

(1). 样板

image-20211111083531075

(2). 在controller包下写一个请求

@RestController
public class AjaxController {

   @RequestMapping("/a1")
//   这个name是从function的data里取数据,而不是从input标签里取数据!!
   public void a1(String name, HttpServletResponse response) throws IOException {
       System.out.println("a1:param---->"+name);
       if("kuangsheng".equals(name)){
           response.getWriter().print("true");
      }else{
           response.getWriter().print("false");
      }
  }
}

(3). 页面的相关代码

我们重点理解三个东西:

  • url

  • data

  • success: function( )

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
 <head>
   <title>$Title$</title>

  <%--引入js文件(jquery)--%>
   <script src="${pageContext.request.contextPath}/static/js/jquery-3.6.0.js"></script>

   <script>
     function a(){
       $.post({
         url:"${pageContext.request.contextPath}/a1", //请求
         //获得下面input标签里的值
         data:{"name":"$(#username).val()"},  //数据
         success: function(data){      //回调函数
           alert(data);
        }

      })
    }

   </script>
 </head>
 <body>

<%--失去焦点的时候,发起一个请求(携带信息)到后台--%>
用户名:<input type="text" id="username" onblur="a()">
 
   </body>
</html>

效果展示:

image-20211111090648786

如果请求不存在呢,就会报404,效果展示如下:

image-20211111091752024

 

(4). 简单工作原理图

image-20211111091431713

 

12.5、Ajax异步加载数据

(1). 编写实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
   private String name;
   private int age;
   private String sex;
}

(2). 编写controller层

//Ajax异步加载
@RequestMapping("/a2")
public List<User> a2(){
   List<User> userList=new ArrayList<User>();

   //添加数据
   userList.add(new User("狂神说Java",1,"男"));
   userList.add(new User("狂神说前端",1,"女"));
   userList.add(new User("狂神说运维",1,"男"));

   //前端用Ajax来异步获取这些数据,展示到页面上

   return userList;
}

(3). 编写test2.jsp页面

注意点:

  • Ajax请求数据--简便写法

  • js操作dom元素去动态的增加节点,将data数据拼接到页面上去

  • 将html语句拼接到id=content的标签里展示$("#content").html(html);

  • 前端变成一个空壳子了,数据都是从js里加载过来了

<%--
 Created by IntelliJ IDEA.
 User: Administrator
 Date: 2021/11/11 0011
 Time: 15:18
 To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>Ajax异步加载数据</title>

  <%--引入js文件(jquery)--%>
   <script src="${pageContext.request.contextPath}/static/js/jquery-3.6.0.js"></script>

   <script>

       //1. 点击button就会触发到这里
       //自执行函数,js一起来就会加载,点击button时就会触发里面的事件
       $(function(){
           $("#btn").click(function(){
               //2. Ajax请求数据--简便写法
               //$.post(url,param[可省略],success)
               $.post("${pageContext.request.contextPath}/a2",function(data){
                   console.log(data);
                   //3. js操作dom元素去动态的增加节点,将data数据拼接到页面上去

                   var html=""

                   for(let i=0;i<data.length;i++){
                       html += "<tr>" +
                           "<td>" +data[i].name +"</td>"+
                           "<td>" +data[i].age +"</td>"+
                           "<td>" +data[i].sex +"</td>"+
                           "<tr>"
                  }
                   //4.将html语句拼接到id=content的标签里展示
                   $("#content").html(html);
              })


          })
      });

   </script>

</head>
<body>

<input type="button" value="加载数据" id="btn">
<table>
   <tr>
       <td>姓名</td>
       <td>年龄</td>
       <td>性别</td>
   </tr>
   <tbody id="content">
  <%--数据在后台,我们要怎么把他展示到前端页面呢?前端变成一个空壳子了,数据都是从js里加载过来了--%>
   </tbody>
</table>
</body>
</html>

 

12.6、Ajax验证用户名登录体验

(1). 编写login.jsp页面

注意点:

  • 使用一定要引入jquery.js文件,不然是没用的!!

  • name就是传去controller的参数,#name就是数据从前端里获取出来


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>Ajax验证用户登录</title>

  <%--引入js文件(jquery)--%>
   <script src="${pageContext.request.contextPath}/static/js/jquery-3.6.0.js"></script>

   <script>
       function a1(){
           $.post({
               url:"${pageContext.request.contextPath}/a3",
               //name就是传去controller的参数,#name就是数据从前端里获取出来
               data:{"name":$("#name").val()},
               success:function(data){
                   //data会存放着从后台返回来的数据
                   if(data.toString()==="ok"){
                       $("#userInfo").css("color","green");
                  }else{
                       $("#userInfo").css("color","red");
                  }
                   $("#userInfo").html(data);

              }
          })

      }
       function a2(){
           $.post({
               url:"${pageContext.request.contextPath}/a3",
               //name就是传去controller的参数,#name就是数据从前端里获取出来
               data:{"pwd":$("#pwd").val()},
               success:function(data){
                   //data会存放着从后台返回来的数据
                   if(data.toString()==="ok"){
                       $("#pwdInfo").css("color","green");
                  }else{
                       $("#pwdInfo").css("color","red");
                  }
                   $("#pwdInfo").html(data);

              }
          })

      }
   </script>
</head>
<body>

<p>
  用户名:<input type="text" id="name" onblur="a1()">
   <span id="userInfo"></span>
</p>
<p>
  密码:<input type="text" id="pwd" onblur="a2()">
   <span id="pwdInfo"></span>
</p>

</body>
</html>

(2). 编写controller层

注意点:

  • name和pwd通过Ajax从前端传过来

//Ajax验证用户登录体验
   @RequestMapping("/a3")
   //name和pwd通过Ajax从前端传过来
   public String a3(String name,String pwd){
   String msg="";
   if(name!=null){
       //admin这些数据应该要从数据库中查出来的
       //现在为了方便测试,就直接在这定义了
       if("admin".equals(name)){
           msg="ok";
      }else{
           msg="用户名有误";
      }
  }
   if(pwd!=null){
       //123456这些数据应该要从数据库中查出来的
       //现在为了方便测试,就直接在这定义了
       if("123456".equals(pwd)){
           msg="ok";
      }else{
           msg="密码有误";
      }
  }
   return msg;
}

 

13. 初识SpringMVC拦截器

13.1、概述

SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。

过滤器与拦截器的区别:拦截器是AOP思想的具体应用。

过滤器

  • servlet规范中的一部分,任何java web工程都可以使用

  • 在url-pattern中配置了/*之后,可以对所有要访问的资源进行拦截

拦截器

  • 拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用

  • 拦截器只会拦截访问的控制器方法, 如果访问的是jsp、html、css、image、js是不会进行拦截的

 

13.2、自定义拦截器

(1). 先编写controller层

@RestController
public class TestController {

   @RequestMapping("/t1")
   public String test(){
       System.out.println("--->test()方法执行了");
       return "ok";
  }
}

(2). 自定义拦截器类

注意点:

  • 要实现HandlerInterceptor接口

  • 实现preHandle方法

//这个接口没有强制我们要实现方法
public class MyInterceptor implements HandlerInterceptor {

   //所有的请求先来到拦截器这里进行相应的过滤
   //return true:执行下一个拦截器,起到放行作用
   //return false:不执行下一个拦截,请求被拦截在这里不能再往下走了,然后你可以通过
   //response转发或重定向去往相应的页面。
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       System.out.println("=========方法执行前=========");
       //这里可以写一些操作,让不符合条件的请求去到它该去的页面!!
       return false;
  }

   //一般我们用第一个就行了,后面两个是用来和日志打交道的!!
   public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
       System.out.println("=========方法执行前=========");
  }

   public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
       System.out.println("=========清理=========");
  }
}

(3). 去到spring配置文件applicationContext.xml里进行配置

注意点:

  • /**:代表这个请求下面的所有的请求

  • /*:代表当前目录的所有子目录,再往下一层就去不到了!!

<!--拦截器配置-->
<mvc:interceptors>
   <mvc:interceptor>
       <!--/**:代表这个请求下面的所有的请求
           /*:代表当前目录的所有子目录,再往下一层就去不到了!!-->
       <mvc:mapping path="/**"/>
       <!--用谁去拦截它-->
       <bean class="com.kuang.config.MyInterceptor"/>
   </mvc:interceptor>
</mvc:interceptors>

 

13.3、拦截器判断登录验证

(1). 编写三个简单页面

main.jsp:首页


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>首页</title>
</head>
<body>
<h1>首页</h1>
<span>${username}</span>
<p>
   <a href="${pageContext.request.contextPath}/user/goOut">注销</a>
</p>
</body>
</html>

login.jsp:


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

<%--在WEB-INF下的所有页面或资源,只能通过controller或servlet来进行转发或重定向进行访问--%>
<h1>登录页面</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">
  用户名:<input type="text" name="username"/>
  密码:<input type="text" name="password"/>
   <input type="submit" value="提交">
</form>
</body>
</html>

index.jsp:


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
 <head>
   <title>$Title$</title>
 </head>
 <body>
 <h1>${pageContext.request.contextPath}</h1>
 <h1><a href="${pageContext.request.contextPath}/user/goLogin">登录页面</a></h1>
 <h1><a href="${pageContext.request.contextPath}/user/main">首页</a></h1>
 </body>
</html>

(2). 编写controller层

package com.kuang.controller;

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

import javax.servlet.http.HttpSession;

@Controller
@RequestMapping("/user")
public class LoginController {

   //登录验证
   @RequestMapping("/login")
   public String login(HttpSession session, String username, String password, Model model){

       //把用户的信息存在session中
       session.setAttribute("userLoginInfo",username);

       //传给前端
       model.addAttribute("username",username);
       return "main";
  }

   //去login页面进行登录
   @RequestMapping("/goLogin")
   public String login(){

       return "login";
  }

   //直接进main页面
   @RequestMapping("/main")
   public String main(){

       return "main";
  }

   //注销
   @RequestMapping("/goOut")
   public String goOut(HttpSession sessioning){

       //sessioning.invalidate(); //这个会把session给毁了,因为用户还在,没必要
       //把session给毁了,你如果毁了,用户又登录时,又要服务器创建session,
       //这很浪费资源

       //所以我们把session移除就行;拦截判断时就没有session了,所以也会被拦住
       System.out.println("走没走这里来ini?");
       sessioning.removeAttribute("userLoginInfo");

       return "login";
  }
}

(3). 编写拦截器

public class LoginInterceptor implements HandlerInterceptor {
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

       HttpSession session=request.getSession();

       //人家准备去登陆页面进行登录,这个也没必要拦住它
       //如果请求路径里面有有关login的请求,那么就让它去登录
       if(request.getRequestURI().contains("goLogin")){
           System.out.println("gonLogin拦截");
           return true;
      }
       //说明用户正在提交登录事务
       if(request.getRequestURI().contains("login")){
           System.out.println("login拦截");
           return true;
      }


       //第一次登录是没有session的,登录过后才有的
       //放行:判断是否已经登录过了,若已经登录过session会有记录的
       if(session.getAttribute("userLoginInfo")!=null){
           System.out.println("session拦截");
           return true;
      }

       System.out.println("在重定向上面……");
       request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
       return false;
  }
}

思路:就拿注销来举例子。你从登录页面登陆进去main页面之后,然后点注销,接着它会根据请求先去拦截器走一回,因为此时还没进控制层里注销方法,所以session还在,故拦截器放他通过了,最后它去找控制器里对应的请求,然后把它的session给移除,通过return返回到login页面重新登录!!

 

14. 文件上传和下载

14.1、准备工作

  • 文件上传是项目开发中最常见的功能之一 ,springMVC 可以很好的支持文件上传,但是SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver。

  • 前端表单要求:为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器;

对表单中的 enctype 属性做个详细的说明:

  • application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。

  • multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。

  • text/plain:除了把空格转换为 "+" 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。

<form action="" enctype="multipart/form-data" method="post">
  <input type="file" name="file"/>
  <input type="submit">
</form>

一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。在2003年,Apache Software Foundation发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。

  • Servlet3.0规范已经提供方法来处理文件上传,但这种上传需要在Servlet中完成。

  • 而Spring MVC则提供了更简单的封装。

  • Spring MVC为文件上传提供了直接的支持,这种支持是用即插即用的MultipartResolver实现的。

  • Spring MVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类:

  • CommonsMultipartResolver。因此,SpringMVC的文件上传还需要依赖Apache Commons FileUpload的组件。

 

14.2、文件上传

(1). 先在pom.xml文件导入相关的包

  • commons-fileupload包

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

servlet的包尽量拿最新的:

<dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>javax.servlet-api</artifactId>
   <version>4.0.1</version>
   <scope>provided</scope>
</dependency>

(2). 编写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">

   <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:applicationContext.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>encoding</filter-name>
       <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
       <init-param>
           <param-name>encoding</param-name>
           <param-value>utf-8</param-value>
       </init-param>
   </filter>
   <filter-mapping>
       <filter-name>encoding</filter-name>
       <url-pattern>/*</url-pattern>
   </filter-mapping>
</web-app>

(3). 在applicationContext.xml配置:

注意点:

  • id是固定的,一定不能写错!因为其他jar包的类要调用它

  • 请求的编码格式必须要和jsp的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1

<!--文件上传的配置-->
<!--id是固定的,一定不能写错!因为其他的类要调用它-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
   <!--请求的编码格式必须要和jsp的pageEncoding属性一致,
       以便正确读取表单的内容,默认为ISO-8859-1
       -->
   <property name="defaultEncoding" value="utf-8"/>
   <!--设置上传文件大小上限,单位为字节(10485760=10M)-->
   <property name="maxUploadSize" value="10485760"/>
   <property name="maxInMemorySize" value="40960"/>
</bean>

(4). 编写一个简单的页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
 <head>
   <title>$Title$</title>
 </head>
 <body>

 <form action="${pageContext.request.contextPath}/upload" enctype="multipart/form-data" method="post">
   <input type="file" name="file">
   <input type="submit" value="upload">
 </form>
 <a href="${pageContext.request.contextPath}/static/哆啦A梦.jpg">下载图片</a>
 </body>
</html>

(5). 编写controller层

注意点:

  • @RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象

  • 获取文件名 : file.getOriginalFilename()

  • 采用file.Transto 来保存上传的文件

  • 这里的文件如果不存在的话,浏览器提示一个网页走丢了的提示,所以要注意!

@RestController
public class FileController {

   //@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
   
   //批量上传CommonsMultipartFile则为数组即可
   
   @RequestMapping("/upload")
   public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException {

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

       //如果文件名为空,直接回到首页!
       if ("".equals(uploadFileName)){
           return "redirect:/index.jsp";
      }
       System.out.println("上传文件名 : "+uploadFileName);

       //上传路径保存设置
       System.out.println("这个upload1路径是哪里"+request.getServletContext());
       String path = request.getServletContext().getRealPath("/upload");
       //如果路径不存在,创建一个
       File realPath = new File(path);
       if (!realPath.exists()){
           realPath.mkdir();
      }
       System.out.println("上传文件保存地址:"+realPath);

       InputStream is = file.getInputStream(); //文件输入流
       OutputStream os = new FileOutputStream(new File(realPath,uploadFileName)); //文件输出流

       //读取写出
       int len=0;
       byte[] buffer = new byte[1024];
       while ((len=is.read(buffer))!=-1){
           os.write(buffer,0,len);
           os.flush();
      }
       os.close();
       is.close();
       return "redirect:/index.jsp";
  }



   /*
    * 采用file.Transto 来保存上传的文件
    */
   @RequestMapping("/upload2")
   public String  fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {

       //上传路径保存设置
       System.out.println("这个upload2路径是哪里"+request.getServletContext());
       String path = request.getServletContext().getRealPath("/upload");
       File realPath = new File(path);
       if (!realPath.exists()){
           realPath.mkdir();
      }
       //上传文件地址
       System.out.println("上传文件保存地址:"+realPath);

       //通过CommonsMultipartFile的方法直接写文件(注意这个时候)
       file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));

       return "redirect:/index.jsp";
  }



   //   下载图片
   @RequestMapping(value="/download")
   public String downloads(HttpServletResponse response , HttpServletRequest request) throws Exception{
       //要下载的图片地址
       String  path = request.getServletContext().getRealPath("/upload");

       //这里的文件如果不存在的话,浏览器提示一个网页走丢了的提示,所以要注意!
       System.out.println("扫描到图片这里了码?");
       String  fileName = "哆啦A梦.jpg";

       //1、设置response 响应头
       response.reset(); //设置页面不缓存,清空buffer
       response.setCharacterEncoding("UTF-8"); //字符编码
       response.setContentType("multipart/form-data"); //二进制传输数据
       //设置响应头
       response.setHeader("Content-Disposition",
                          "attachment;fileName="+ URLEncoder.encode(fileName, "UTF-8"));

       File file = new File(path,fileName);
       //2、 读取文件--输入流
       InputStream input=new FileInputStream(file);
       //3、 写出文件--输出流
       OutputStream out = response.getOutputStream();

       byte[] buff =new byte[1024];
       int index=0;

       //4、执行 写出操作
       while((index= input.read(buff))!= -1){
           out.write(buff, 0, index);
           out.flush();
      }
       out.close();
       input.close();
       return "ok";
  }

 

15. ssm框架对静态资源的访问(.css,.js,.html,照片等)

15.1、环境导入

优雅REST风格的资源URL不希望带 .html 或 .do 等后缀.由于早期的Spring MVC不能很好地处理静态资源,所以在web.xml中配置DispatcherServlet的请求映射,往往使用 *.do 、 *.xhtml等方式。这就决定了请求URL必须是一个带后缀的URL,而无法采用真正的REST风格的URL。

如果将DispatcherServlet请求映射配置为"/",则Spring MVC将捕获Web容器所有的请求,包括静态资源的请求,Spring MVC会将它们当成一个普通请求处理,因此找不到对应处理器将导致错误。

如何让Spring框架能够捕获所有URL的请求,同时又将静态资源的请求转由Web容器处理,是可将DispatcherServlet的请求映射配置为"/"的前提。由于REST是Spring3.0最重要的功能之一,所以Spring团队很看重静态资源处理这项任务,给出了堪称经典的两种解决方案。

(1). 先调整web.xml中的DispatcherServlet的配置,使其可以捕获所有的请求:

<servlet>
       <servlet-name>springMVC</servlet-name>
       <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
       <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
       <servlet-name>springMVC</servlet-name>
       <url-pattern>/</url-pattern>
   </servlet-mapping>

通过上面url-pattern的配置,所有URL请求都将被Spring MVC的DispatcherServlet截获

 

(2). 方法一:采用<mvc:default-servlet-handler />,这个方法有时会不生效,所以建议还是使用下面那个方法!!

<mvc:default-servlet-handler />

在springMVC-servlet.xml中配置<mvc:default-servlet-handler />后,会在Spring MVC上下文中定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会像一个检查员,对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。

一般Web应用服务器默认的Servlet名称是"default",因此DefaultServletHttpRequestHandler可以找到它。如果你所有的Web应用服务器的默认Servlet名称不是"default",则需要通过default-servlet-name属性显示指定:

<mvc:default-servlet-handler default-servlet-name="所使用的Web服务器默认使用的Servlet名称" />

 

(3). 方法二:采用<mvc:resources /> 重新映射路径 (推荐使用!!)

<mvc:default-servlet-handler />将静态资源的处理经由Spring MVC框架交回Web应用服务器处理。而<mvc:resources />更进一步,由Spring MVC框架自己处理静态资源,并添加一些有用的附加值功能。

首先,<mvc:resources />允许静态资源放在任何地方,如WEB-INF目录下、类路径下等,你甚至可以将JavaScript等静态文件打到JAR包中。通过location属性指定静态资源的位置,由于location属性是Resources类型,因此可以使用诸如"classpath:"等的资源前缀指定资源位置。传统Web容器的静态资源只能放在Web容器的根路径下,<mvc:resources />完全打破了这个限制。

其次,<mvc:resources />依据当前著名的Page Speed、YSlow等浏览器优化原则对静态资源提供优化。你可以通过cacheSeconds属性指定静态资源在浏览器端的缓存时间,一般可将该时间设置为一年,以充分利用浏览器端的缓存。在输出静态资源时,会根据配置设置好响应报文头的Expires 和 Cache-Control值。

在接收到静态资源的获取请求时,会检查请求头的Last-Modified值,如果静态资源没有发生变化,则直接返回303相应状态码,提示客户端使用浏览器缓存的数据,而非将静态资源的内容输出到客户端,以充分节省带宽,提高程序性能。

在springMVC-servlet中添加如下配置(同时配置多个):

<mvc:resources location="/,classpath:/META-INF/publicResources/" mapping="/resources/**"/>

以上配置将Web根路径"/"及类路径下 /META-INF/publicResources/ 的目录映射为/resources路径。假设Web根路径下拥有images、js这两个资源目录,在images下面有bg.gif图片,在js下面有test.js文件,则可以通过 /resources/images/bg.gif 和 /resources/js/test.js 访问这二个静态资源。

假设WebRoot还拥有images/bg1.gif 及 js/test1.js,则也可以在网页中通过 /resources/images/bg1.gif 及 /resources/js/test1.js 进行引用。

例子:

<!-- 对静态资源文件的访问  restful-->     
<mvc:resources mapping="/admin/**" location="/,/admin/" />
<mvc:resources mapping="/static/**" location="/,/static/" />
<mvc:resources mapping="/plugins/**" location="/,/plugins/" />
<mvc:resources mapping="/uploadFiles/**" location="/,/uploadFiles/" />

 

 

 

posted @ 2021-12-24 15:05  周游码世界  阅读(94)  评论(0)    收藏  举报