SpringMVC

1. SpringMVC概述

SpringMVC是Spring框架中的一个模块,它是一个基于Java的MVC框架,是Spring的后端 MVC 框架。SpringMVC的主要作用是用来开发基于web的应用,通过MVC模式将业务逻辑、数据访问层、业务逻辑层和表示层分离,并通过一个中心控制器Servlet来集中处理请求。

MVC模式:

  • Model:模型层,主要用来封装数据,比如POJO对象。
  • View:视图层,用来展现数据,比如JSP页面。
  • Controller:控制器层,用来处理用户请求,比如Servlet。

MVC框架优点:

  • 封装了Servlet API,简化了开发难度。
  • 接收请求方便(一个类中,不同的方法可以接收不同的请求,而Servlet一个类只能处理一个请求)
  • 接收请求数据方便(自动封装)
  • 处理响应数据方便(自动响应JSON)

springmvc执行流程.jpg

MVC执行流程

简单来说,SpringMVC的执行流程如下:

  • 客户端发送请求至前端控制器DispatcherServlet。
  • DispatcherServlet收到请求调用HandlerMapping查找Handler。
  • HandlerMapping根据请求url找到具体的Handler。
  • Handler对请求进行处理,并返回 ModelAndView。
  • ModelAndView包含 ModelAndView.view和ModelAndView.model两个属性。
  • DispatcherServlet将ModelAndView传给ViewReslover。
  • ViewReslover解析后端模板并渲染视图,并将结果返回给DispatcherServlet。
  • DispatcherServlet将渲染结果返回给客户端。

2. SpringMVC入门

入门演示使用配置文件完成演示。完全按照springmvc执行流程进行配置演示。

2.1 新建项目

新建一个项目,需要是javaWeb项目,因为mvc主要是基于web开发。

创建javaWeb项目.png

2.2 导入依赖

导入SpringMVC依赖。

  <dependencies>
<!--    配置springmvc及其他核心依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.3.33</version>
    </dependency>
<!--    导入servlet-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
    </dependency>
  </dependencies>

2.3 前端控制器DispatcherServlet

前端控制器是SpringMVC的核心组件,它是整个SpringMVC的入口,负责接收请求、分派请求到相应的Controller,并将Controller的处理结果生成ModelAndView对象,再将ModelAndView对象传给相应的View进行渲染。

在web.xml中配置前端控制器:

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

<web-app>
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--      前端发起请求以后,映射到DispatcherServlet,就要找HandlerMapping,以及HandleAdapter-->
<!--      这些组件都在springmvc的配置文件中,所以我们需要dispatcherServlet找到配置文件-->
<!--      所以需要加载springmvc.xml配置文件-->
      <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc.xml</param-value>
      </init-param>
  </servlet>

  <servlet-mapping>
      <servlet-name>dispatcherServlet</servlet-name>
<!--    请求路径是/,会拦截到所有请求,包括html、js、css等请求,所以一般不这样写-->
<!--    <url-pattern>/</url-pattern>-->
<!--        请求后缀为.do的请求-->
      <url-pattern>*.do</url-pattern>
  </servlet-mapping>
</web-app>

2.4 创建Controller

创建一个Controller,用来处理请求。

package org.demo.controller;

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

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

public class HandleController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 1. 接受请求参数
        // 2. 调用service处理请求
        // 3. 响应结果
        System.out.println("执行类。。。。");
        ModelAndView mv = new ModelAndView();
        mv.setViewName("user");
        return mv;
    }
}

2.5 配置springmvc.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">

<!--    注册controller-->
    <bean id="handleController" class="org.demo.controller.HandleController" name="/user.do"/>

<!--    不要id也行,因为没有别的地方需要引用-->
<!--    处理器映射器-->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!--        处理器适配器-->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>

<!--    视图解析器-->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--        用来拼接页面的前缀-->
        <property name="prefix" value="/"/>
<!--            后缀-->
        <property name="suffix" value=".html"/>
    </bean>
</beans>

2.6 运行项目

运行项目,访问http://localhost:6060/user.do,可以看到浏览器正确响应返回,说明项目运行成功。

tomcat运行.jpg

3. 注解开发SpringMVC

SpringMVC的注解开发方式,可以更加方便的开发SpringMVC项目。

基本步骤同上,不再赘述,只给出不同的部分。(创建项目、依赖、web.xml配置不需要改变)

3.1 创建Controller

package org.demo.controller;


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

@Controller
public class HelloController{
    @RequestMapping("/hello.do")
    public String hello(){
        return "hello";
    }
}


3.2 更改springmvc.xml

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


<!--    扫描注解-->
    <context:component-scan base-package="org.demo.controller"/>

<!--    mvc注解开发驱动,用来取代映射器,和适配器-->
    <mvc:annotation-driven/>

<!--    视图解析器-->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--        用来拼接页面的前缀-->
        <property name="prefix" value="/"/>
<!--            后缀-->
        <property name="suffix" value=".html"/>
    </bean>
</beans>

3.3 运行项目

运行项目,访问http://localhost:6060/hello.do,可以看到浏览器正确响应返回,说明项目运行成功。

tomcat运行2.jpg

tip@RequestMapping注解,用来映射请求路径,可以指定请求方法,如@RequestMapping(value = "/hello.do", method = RequestMethod.GET),表示只处理GET请求等。还可以将@RequestMapping注解放在类上,表示类中的所有方法都映射到请求路径。如在类上加上@RequestMapping("/user"),表示类中所有方法都映射到/user请求路径,然后方法上加上@RequestMapping("/add"),表示该方法映射到/user/add请求路径,以此类推。类似的还有@GetMapping@PostMapping@PutMapping@DeleteMapping等注解,分别对应GET、POST、PUT、DELETE请求。

4. 参数绑定

所谓参数绑定,就是前端发请求中的数据,可以在Controller的方法参数中接收,即前端请求数据方法参数绑定。

4.1 接收基本数据类型

接收基本数据类型,如int、String、double、date等。

  1. 编写前端页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>

<!--<a href="/hello.do">欢迎你</a>-->

<h1>接收基本数据类型</h1>
<a href="/sendBase.do?id=1&name=张三&money=10.5&birthday=2022-01-01">发送数据</a>
<form action="sendBase.do">
    id:<input type="text" name="id" /><br/>
    name:<input type="text" name="name"/><br/>
    money:<input type="text" name="money"/><br/>
    birthday:<input type="date" name="birthday"/><br/>
    <input type="submit" value="from表单发送数据"/>
</form>


</body>
</html>
  1. 编写Controller
package org.demo.controller;

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

@Controller
public class DataController {
    @RequestMapping("/sendBase.do")
    public String base(int id,String name,double money) {
        System.out.println("id:" + id + ",name:" + name + ",money:" + money + ",birthday:" + birthday);
        return "OK";
    }
}

  1. 测试
    基本类型日期错误.jpg

原因:默认接收的日期格式是yyyy/MM/dd,而我们发送的日期格式是yyyy-MM-dd,所以会出现日期格式不匹配的错误。

解决办法有两种:

  • 修改前端发送的日期格式,手动将日期格式从yyyy-MM-dd改为yyyy/MM/dd。
  • 在Controller方法参数添加注解@DateTimeFormat(pattern="yyyy-MM-dd"),指定接收日期格式。

可以看到,对日期添加注解后,指定日期格式为yyyy-MM-dd,可以正确接收日期数据。
基本数据类型成功.jpg

tip:主要前端传递的参数要和后端方法参数一致,否则会出现参数绑定失败的错误。

4.2 接收对象类型

接收对象类型其实很简单,只需要确保传递的参数和对象的属性名一致即可。

  1. 创建一个User对象
package org.demo.entity;

import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;
import java.util.Date;


public class User implements Serializable {
    private Integer id;
    private String name;
    private Double money;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday;

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    public Date getBirthday() {
        return birthday;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                ", birthday=" + birthday +
                '}';
    }
}

  1. 创建Controller方法
    @RequestMapping("/sendObj.do")
    public String getObj(User user) {
        System.out.println(user);
        return "OK";
    }
  1. 前端页面改写一下
<h1>接收对象</h1>
<form action="/sendObj.do" method="get">
    id:<input type="text" name="id" /><br/>
    name:<input type="text" name="name"/><br/>
    money:<input type="text" name="money"/><br/>
    birthday:<input type="date" name="birthday"/><br/>
    <input type="submit" value="from表单发送对象数据"/>
</form>
  1. 测试
    接收对象成功.jpg

可以看到控制台打印出了对象的相关信息,说明参数绑定成功。

注意,我们这里是使用GET请求,如果是POST请求,我们接收对象后会发现,中文是乱码的,如下:

User{id=2, name='测试', money=117.5, birthday=Wed Nov 06 00:00:00 HKT 2024}

为了解决这个问题,我们需要在web.xml中配置全局编码

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

这时再收到请求,中文就正常显示了。

4.3 接收数组类型

学习接收数组类型之前,要先清除,什么场景下使用,方便我们更好的学习与理解。在实际项目中,批量删除,批量修改等操作,一般都是使用数组来接收参数。

  1. 编写前端页面
<h1>接收数组</h1>
<form action="/sendAry.do" method="get">
    听音乐:<input type="checkbox" name="ids" value="1" /><br/>
    看电影:<input type="checkbox" name="ids" value="2"/><br/>
    打篮球:<input type="checkbox" name="ids" value="3"/><br/>
    <input type="submit" value="from表单发送数组数据"/>
</form>
  1. 编写Controller方法
    @RequestMapping("/sendAry.do")
    public String getAry(int[] ids) {
        System.out.println(Arrays.toString(ids));
        return "OK";
    }
  1. 测试
[1, 2]

可以看到,使用复选框来实现批量操作,并通过数组接收参数,可以正常接收数组数据。

tip:注意name属性的值,要和Controller方法参数名一致,否则参数绑定失败。上面都是ids,如果不一致,后端则会获取为null。

4.4 接收List集合类型

接收list集合的场景类似于接受数组,只是后端处理时接收的数据类型为list。

  1. 编写前端页面
<h1>接收List集合</h1>
<form action="/sendList.do" method="get">
    听音乐:<input type="checkbox" name="list" value="听音乐" /><br/>
    看电影:<input type="checkbox" name="list" value="看电影"/><br/>
    打篮球:<input type="checkbox" name="list" value="打篮球"/><br/>
    <input type="submit" value="from表单发送List数据"/>
</form>
<hr/>
  1. 编写Controller方法
    @RequestMapping("/sendList.do")
    public String getList(List<String> list) {
        System.out.println(list);
        return "OK";
    }
  1. 测试
Type 异常报告

消息 Request processing failed; nested exception is java.lang.IllegalStateException: No primary or single unique constructor found for interface java.util.List

描述 服务器遇到一个意外的情况,阻止它完成请求。

Exception

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: No primary or single unique constructor found for interface java.util.List

测试发现,接收List数据失败,这是因为SpringMVC默认不支持接收List集合,需要我们自己配置。

这时候有两种解决方案:

  • 使用@RequestParam注解,接收List集合。
  • 使用实体类,封装List集合,在进行接收。

先演示第一种解决方案。只需要在方法参数上加上注解即可。

    @RequestMapping("/sendList.do")
    public String getList(@RequestParam List<String> list) {
        System.out.println(list);
        return "OK";
    }

第二种解决方案,在实体类User中添加List集合属性。

package org.demo.entity;

import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;
import java.util.Date;
import java.util.List;


public class User implements Serializable {
    private Integer id;
    private String name;
    private Double money;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday;

    private List<String> list;


    //这里省略get、set方法,不再给出

修改一下后端Controller方法。

    @RequestMapping("/sendList.do")
    public String getList(User user) {
        System.out.println(user.getList());
        return "OK";
    }

这时候再进行测试即可。

[听音乐, 看电影]

可以看到无论是使用注解,还是在实体类中封装属性,都正常接收到了List集合的数据。

4.5 接收Map集合类型

map集合的主要场景就是用于接收对象,对象不确定属性值,这时我们就可以使用map接收。

  1. 编写前端页面
<h1>接收Map集合</h1>
<form action="/sendMap.do" method="get">
    id:<input type="text" name="id" /><br/>
    name:<input type="text" name="name"/><br/>
    money:<input type="text" name="money"/><br/>
    birthday:<input type="date" name="birthday"/><br/>
    <input type="submit" value="from表单发送Map数据"/>
</form>
<hr/>
  1. 编写Controller方法(因为同样mvc不支持map,所以需要加注解@requestParam)
    @RequestMapping("/sendMap.do")
    public String getMap(@RequestParam Map<String, Object> map) {
        System.out.println(map);
        return "OK";
    }
  1. 测试
{id=2, name=万里, money=117.5, birthday=2024-11-28}

可以看到,接收map集合成功,打印出了map集合的数据。可是这样子并没有体现出map集合接收对象和之前接收对象有什么区别,我们可以把前端页面的属性值改下一下,比如:

<h1>接收Map集合</h1>
<form action="/sendMap.do" method="get">
    id:<input type="text" name="ids" /><br/>
    name:<input type="text" name="uname"/><br/>
    money:<input type="text" name="balance"/><br/>
    birthday:<input type="date" name="bday"/><br/>
    <input type="submit" value="from表单发送Map数据"/>
</form>
<hr/>

再次测试输出

{ids=23, uname=大胆, balance=11.23, bday=2024-11-13}

可以看到,还是可以正常接收。如果使用对象接收的话,前提必须是对象中的属性和前端页面中设置的属性名一致,否则参数绑定失败,但是使用map集合来接收就不存在这个问题,因为map集合不关心属性名,只关心key-value。所以map经常用于模糊查询,字段属性不明确的场景。

5. 页面跳转

页面跳转,可以分为两种,一种是请求转发(forward),一种是重定向(redirect)。他们最大的区别就是,转发只发出一个请求,浏览器地址不会发生改变,重定向则会发出两个请求,其中一个新的请求去完成操作,浏览器地址发生改变。

想要使用转发重定向,需要使用对应的关键字:forward:/redirect:/。接下来使用一个例子来演示如何使用转发和重定向来实现页面跳转和请求转发。

  1. 编写Controller方法
package org.demo.controller;


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

@Controller
public class SkipController {


    @RequestMapping("/forward.do")
    public String forward(){
        System.out.println("执行请求转发页面");
        return "forward:/OK.html";
    }

    @RequestMapping("/forward2.do")
    public String forward2(){
        System.out.println("执行请求转发请求");
        return "forward:/hello.do";
    }

    @RequestMapping("/redirect.do")
    public String redirect(){
        System.out.println("重定向页面");
        return "redirect:/OK.html";
    }
    @RequestMapping("/redirect2.do")
    public String redirect2(){
        System.out.println("重定向请求");
        return "redirect:/hello.do";
    }

}
  1. 测试

具体效果不再展示,自己尝试一下就可以看到,转发不论是请求还是页面,地址都没有发生改变,而重定向地址则改变为了具体的页面或请求。

6. 数据共享

数据共享是指数据域,即为request域和session域,它们提供一个共享的存储空间,可以让多个请求之间共享数据。

  • request域:用于保存请求相关的数据,在一次请求中,可以共享数据,不同请求之间的数据互不干扰
  • session域:用于保存用户相关的数据,在一次会话中,可以共享数据,不同会话之间的数据互不干扰

使用例子演示一下:

  1. 编写Controller方法
package org.demo.controller;


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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;


/**
 * 演示Request和Session 数据域,实现数据共享
 */

@Controller
public class ScopeController {

    @RequestMapping("req.do")
    public String req1(HttpServletRequest request, HttpSession session){
        request.setAttribute("msg","存入request域中");
        session.setAttribute("msg","存入session域中");
        return "OK";
    }

    @RequestMapping("reqWithFor.do")
    public String req2(HttpServletRequest request, HttpSession session){
        request.setAttribute("msg","转发存入request域中");
        session.setAttribute("msg","转发存入session域中");
        return "forward:/req1.do";
    }


    @RequestMapping("req1.do")
    public String req3(HttpServletRequest request, HttpSession session){
        Object msg = request.getAttribute("msg");
        System.out.println(msg);
        Object msg1 = session.getAttribute("msg");
        System.out.println(msg1);
        return "OK";
    }


}

  1. 测试
null
存入session域中
转发存入request域中
转发存入session域中

先请求req.do,在请求req1.do,最后请求reqWithFor.do,从输出可以看到,使用req.do存入数据后,使用req1.do后,正确取出了session域的数据,由于是两次请求,request域数据不共享,所以为null,而使用reqWithFor.do后,request域数据被转发到req1.do,视为一次请求,所以正确取出了request域和session域的数据,实现了数据共享。

7. 静态资源处理

静态资源处理,主要是指处理静态资源,比如图片、css、js等文件。

首先编写一个静态页面,引入js文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>js</title>
    <style>
        #bg {
            width: 200px;
            height: 200px;
            background-color: #ff0000;
        }

    </style>
</head>
<body>
<div id="bg"></div>
<button onclick="changeColor()">点击切换颜色</button>
</body>

<script src="js/jquery-1.8.3.min.js"></script>
<script type="application/javascript">
    function changeColor() {
        var color = "#" + Math.random().toString(16).substring(2, 8);
        $("#bg").css("background-color", color);
    }
</script>

</html>

直接在浏览器中访问页面,可以正常访问,这是因为在此次案例中,我们在web.xml文件中配置了只匹配.do结尾的请求,所以静态资源不受影响,但是如果我们将.do改为/后,发现静态资源就无法访问了。此时就需要我们对静态资源进行处理。

  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <!-- <url-pattern>*.do</url-pattern> -->
    <url-pattern>/</url-pattern>
  </servlet-mapping>

其实很简单,只需要我们在mvc配置文件springmvc.xml中配置一下静态资源映射即可。

    <!-- /** 表示匹配所有路径。也就是说,任何以 / 开头的请求都会被这个配置匹配。
     
     location="/":这个属性指定了静态资源的存放位置。这里的 / 表示在根目录下查找静态资源。这通常是指项目的根目录或类路径下的资源。-->
     <mvc:resources mapping="/**" location="/"/>

这段配置的作用是:将所有请求(匹配 /** 的请求)都转发到项目根目录下查找静态资源。这意味着,浏览器访问任何静态资源时,都会按照该映射在指定的位置查找,从而保证静态资源能够正常访问。

8. 拦截器

8.1 定义拦截器

  • 自定义类
  • 实现接口
  • 重写方法
  • springmvc.xml配置
  1. 自定义类,实现HandlerInterceptor接口,重写三个方法
package org.demo.interceptor;

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

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

public class MyInterceptor implements HandlerInterceptor {

    /**
     * 前处理  预处理
     * Controller执行之前  执行pre
     * @param request
     * @param response
     * @param handler
     * @return  返回true 认为放行,返回false,认为拦截
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    /**
     * 目标Controller执行完之后  但是又在afterCompletion执行之前
     * 一般用于校验
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("post方法");
    }

    /**
     * Controller执行后 再执行
     * 对目标方法的返回 在进行定制处理
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("after方法");
    }
}
  1. 配置springmvc.xml
    <mvc:interceptors>
        <mvc:interceptor>
<!--            要拦截的路径-->
            <mvc:mapping path="/**"/>
<!--            排除的路径,直接放行-->
            <mvc:exclude-mapping path="/hello.do"/>
            <bean class="org.demo.interceptor.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

9. 异常处理

springmvc框架提供了一个全局的异常处理机制,可以处理controller层抛出的所有异常,这种方法叫做集中管理异常。

编写类,实现HandlerExceptionResolver接口,重写方法即可

package org.demo.util;

import org.demo.exception.AutoException;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

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

/**
 * 通过Component注解将该异常类添加到spring容器中
 */
@Component
public class MyExceptionHandle implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        String message = ex.getMessage();
        System.out.println("message:" + message);
        if(ex instanceof ArithmeticException) return new ModelAndView("redirect:/error.html");
        else if (ex instanceof AutoException)return new ModelAndView("redirect:/500.html");
        // 具体业务具体使用
        return null;
    }
}

10. JSON处理【重中之重】

无论是前端还是后端,都常使用json数据来进行交互,所以我们本节重点学习json数据的处理。springmvc支持json数据交互,但是自己本身没有对应jar,使用的是第三方的jackson等。

tip:如果使用阿里巴巴的fastjson来处理json数据,需要在pom.xml中添加依赖,并且进行配置。

10.1 响应JSON数据

一般后端响应json数据,都需要有一个固定的模板,比如msg,code,data等字段,然后将数据封装成json格式,返回给前端。

  1. 封装code值
package org.demo.util;

public class ResultCode {
    public static final Integer SUCCESS = 200;
    public static final int NOT_FOUND = 404;
    public static final int ERROR = 500;
    public static final int UNAUTHENTICATED = 401;
    public static final int UNAUTHORIZED= 403;
}

  1. 编写json封装模板类
package org.demo.util;

import lombok.Data;
import lombok.experimental.Accessors;

/**
 * @Data 注解会自动生成以下方法:
 * - Getter 和 Setter 方法
 * - toString 方法
 * - equals 和 hashCode 方法
 * @Accessors(chain = true) 注解用于生成链式调用的 setter 方法。
 * 每个 setter 方法都会返回当前对象的引用 (this),从而支持链式调用。
 */
@Data
@Accessors(chain = true)
public class ResultDate {
    private Integer code;
    private String msg;
    private Object data;
    private Boolean success;


    private ResultDate(){}

    public static ResultDate ok(){
        return new ResultDate().setCode(ResultCode.SUCCESS).setSuccess(true).setMsg("成功");
    }

    public static ResultDate ok(Object data){
        return new ResultDate().setCode(ResultCode.SUCCESS).setSuccess(true).setMsg("成功").setData(data);
    }

    public  static  ResultDate fail(){
        return new ResultDate().setCode(ResultCode.UNAUTHENTICATED).setSuccess(false).setMsg("身份认证失败,无权限");
    }
}
  1. 编写Controller方法
package org.demo.controller;

import org.demo.entity.User;
import org.demo.util.ResultDate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

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

@Controller
public class TestController {

    /**
     * 处理 /test.do 请求,返回一个简单的 JSON 字符串。
     * @return 返回一个包含 "msg" 和 "code" 的 JSON 字符串
     */
    @RequestMapping("/test.do")
    @ResponseBody
    public String test() {
        System.out.println("测试-OK");
        return "{\"msg\":\"OK\",\"code\":200}";
    }

    /**
     * 处理 /json1.do 请求,返回一个成功的 ResultDate 对象。
     * @return 返回一个成功的 ResultDate 对象
     */
    @RequestMapping("/json1.do")
    @ResponseBody
    public ResultDate json1() {
        System.out.println("测试json1-OK");
        return ResultDate.ok();
    }

    /**
     * 处理 /json2.do 请求,返回一个包含用户列表的成功的 ResultDate 对象。
     * @return 返回一个包含用户列表的成功的 ResultDate 对象
     */
    @RequestMapping("/json2.do")
    @ResponseBody
    public ResultDate json2() {
        System.out.println("测试json2-OK");
        List<User> list = new ArrayList<>();
        int i = 0;
        while (i++ < 5) {
            list.add(new User()
                    .setId(i)
                    .setName("大海" + i + "号")
                    .setAge(18 + i)
                    .setPhone("1322222222" + i)
                    .setAddress("山西省太原市迎泽区"));
        }
        return ResultDate.ok().setData(list);
    }

    /**
     * 处理 /json3.do 请求,返回一个失败的 ResultDate 对象。
     * @return 返回一个失败的 ResultDate 对象
     */
    @RequestMapping("/json3.do")
    @ResponseBody
    public ResultDate json3() {
        System.out.println("测试json3-fail");
        return ResultDate.fail();
    }
}

  1. 测试
{"code":200,"msg":"成功","data":null,"success":true}
================
{"code":200,"msg":"成功","data":[{"id":1,"name":"大海1号","age":19,"address":"山西省太原市迎泽区","phone":"13222222221"},{"id":2,"name":"大海2号","age":20,"address":"山西省太原市迎泽区","phone":"13222222222"},{"id":3,"name":"大海3号","age":21,"address":"山西省太原市迎泽区","phone":"13222222223"},{"id":4,"name":"大海4号","age":22,"address":"山西省太原市迎泽区","phone":"13222222224"},{"id":5,"name":"大海5号","age":23,"address":"山西省太原市迎泽区","phone":"13222222225"}],"success":true}
================
{"code":401,"msg":"身份认证失败,无权限","data":null,"success":false}

tip:使用@responsebody注解,可以将返回的数据直接转换为json格式,不再经过视图解析器,如果不加这个注解,默认返回的是视图,所以会报错。除此之外,还有个@RestController注解,它是@Controller@ResponseBody组合注解。使用 @RestController 标记的控制器类中的所有方法默认都会应用 @ResponseBody 注解。

10.2 解析JSON数据

10.2.1 简单类型

前端发送的json数据,后端使用简单类型(基本类型、String)直接接收。

  1. 编写前端页面
<h1>ajax-json-简单类型</h1>
<button id="btn1">简单类型</button>

<script src="js/jquery-1.8.3.min.js"></script>
<script type="application/javascript">
    $("#btn1").click(
        $.ajax({
            url:"/base.do",
            type:"get",
            data:{"id":1,"name":"张三"},
            success:function (data) {
                alert(JSON.stringify(data));
            },
            error:function () {
                alert("error");
            }
        })
    );

</script>
  1. 编写Controller方法
    @RequestMapping("/base.do")
    @ResponseBody
    public ResultDate base(Integer id,String name) {
        System.out.println("测试解析基本类型");
        System.out.println("id = " + id + ", name = " + name);
        return ResultDate.ok();
    }
  1. 测试
测试解析基本类型
id = 1, name = 张三

10.2.2 对象类型

前端发送json数据,后端使用对象来接收。

  1. 编写前端页面
<h1>ajax-json-对象</h1>
<button id="btn2">对象</button>
<script src="js/jquery-1.8.3.min.js"></script>
<script type="application/javascript">

        $("#btn2").click(function (){
            $.ajax({
                url:"/obj.do",
                type:"POST",
                data:{"id":1,"name":"张三","address":"陕西省西安市","phone":"13888888888","age":18},
                success:function (data) {
                    alert(JSON.stringify(data));
                },
                error:function () {
                    alert("error");
                }
            })
        });

</script>


  1. 编写Controller方法
    @RequestMapping("/obj.do")
    @ResponseBody
    public ResultDate obj(User user) {
        System.out.println("测试解析对象");
        System.out.println(user);
        return ResultDate.ok();
    }
  1. 测试
测试解析对象
User(id=1, name=张三, age=18, address=陕西省西安市, phone=13888888888)

tip:使用对象接收json数据,同之前对象参数绑定一样,前端发送数据的字段名和对象属性名要一致。如果不一致,那就会绑定失败。假如我将前端发送的数据字段改为address1,那么address就会绑定失败。另外要注意日期属性,前端发送日期需要yyyy/MM/dd格式,而不是yyyy-MM-dd。除非后端你进行了处理(添加注解),那么可以正常写。

User(id=1, name=张三, age=18, address=null, phone=13888888888)

这时我们观察浏览器发起的请求可以发现,我们传递的参数为

id=1&name=%E5%BC%A0%E4%B8%89&address=%E9%99%95%E8%A5%BF%E7%9C%81%E8%A5%BF%E5%AE%89%E5%B8%82&phone=13888888888&age=18

可是我们明明在data里写的是json格式,为什么发送的时候会是以字符串发送呢?很简单,我们查看请求发送的协议头,发现content-typeapplication/x-www-form-urlencoded,也就是表单提交,因为我们没有填写发送的内容类型,默认给我们按照了表单提交,但是我们后端返回的是json数据,所以响应头里的content-typeapplication/json,这样子才是真正的json,所以我们对前端ajax进行修改,使其发送真正的json数据。

<script type="application/javascript">

        $("#btn2").click(function (){
            $.ajax({
                url:"/obj.do",
                type:"POST",
                data:{"id":1,"name":"张三","address":"陕西省西安市","phone":"13888888888","age":18},
                contentType:"application/json",//只需要添加这一行即可
                success:function (data) {
                    alert(JSON.stringify(data));
                },
                error:function () {
                    alert("error");
                }
            })
        });

</script>

这时候我们再来测试请求查看一下,发现请求头确实变成了application/json,但是这时候又发现,后端接收的对象,是空的。

测试解析对象
User(id=null, name=null, age=null, address=null, phone=null)

这时候我们需要在后端方法上加上注解@RequestBody,这样子,前端发送的json数据就会自动解析并且绑定到对象上。

    @RequestMapping("/obj.do")
    @ResponseBody
    public ResultDate obj(@RequestBody User user) {
        System.out.println("测试解析对象");
        System.out.println(user);
        return ResultDate.ok();
    }

注意,这时我们需要修改前端data,使用JSON.stringify()方法将对象转换为json字符串,否则后端会报错JSON parse error: Unrecognized token 'id': was expecting (JSON String, Number, Arra....也就是JSON不规范。

<script type="application/javascript">

        $("#btn2").click(function (){
            $.ajax({
                url:"/obj.do",
                type:"POST",
                data:JSON.stringify({"id":1,"name":"张三","address":"陕西省西安市","phone":"13888888888","age":18}),
                contentType:"application/json",
                success:function (data) {
                    alert(JSON.stringify(data));
                },
                error:function () {
                    alert("error");
                }
            })
        });
</script>

这时再次运行测试,可以看到后端从json数据中正确取出了User对象。

测试解析对象
User(id=1, name=张三, age=18, address=陕西省西安市, phone=13888888888)

11. 文件上传

在实际开发中,经常遇到需要上传文件的场景,图片,文档等等。主要使用的是第三方的依赖,commons-fileuploadcommons-io

11.1 上传文件步骤

  1. 导入依赖
<!--    导入文件上传依赖-->
<!-- 因为commons-fileupload已经包含了io,所以不再需要导入 -->
    <!-- <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.17.0</version>
    </dependency> -->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.3</version>
      <!--      排除,防止依赖冲突-->
      <exclusions>
        <exclusion>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  1. springmvc.xml配置文件解析器
<!--    配置文件上传解析器 必须加id,不然会报错-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--        最大文件大小  字节为单位  50MB = 1024*1024*50 -->
        <property name="maxUploadSize" value="52428800"/>
    </bean>
  1. 编写Controller方法
    /**
     * 参数类型必须是MultipartFile 参数名和前端必须一致  这样文件上传时,内部就会有文件对象
     * @param file
     * @param request
     * @return
     */
    @RequestMapping("/upload.do")
    @ResponseBody
    public ResultDate upload(MultipartFile file, HttpServletRequest request) throws IOException {

        System.out.println("文件的大小为:" + file.getSize());
        System.out.println("文件的类型为:" + file.getContentType());
        String fileName = file.getOriginalFilename();
        //获取服务器文件路径
        String path = request.getServletContext().getRealPath("/upload_file");
        System.out.println(path);
        File parentFile = new File(path);
        if (!parentFile.exists()) {
            //不存在,就创建文件夹
            parentFile.mkdir();
        }
        //生成随机uuid,确保名称唯一,主要图片使用
        String unique = UUID.randomUUID().toString();

        //获取文件后缀
        String ext = FilenameUtils.getExtension(fileName);

        String uniqueName = unique + "." + ext;
        System.out.println("唯一的文件名为:" + uniqueName);

        //上传文件到文件夹
        file.transferTo(new File(parentFile, uniqueName));
        return ResultDate.ok("上传文件成功");
    }
  1. 测试结果
文件的大小为:102
文件的类型为:text/plain
C:\Users\17634\Desktop\Java基础入门笔记\SSM\SpringMvc\spingmvc2\target\spingmvc2\upload_file
唯一的文件名为:2e3d6d51-ee16-46f9-9e0b-b864f240f1ad.txt

然后查看自己项目的upload_file文件夹,可以看到上传的文件。

11.2 上传后回显

图片上传成功,通常需要将文件回显给前端,前端进行显示,这时候通常有两种方案:

  • 方案一:将文件路径回显给前端,前端根据路径进行显示。
  • 方案二:使用ajax上传成功后,后端返回json数据到回调函数中,前端进行处理显示。
  1. 编写controller方法,返回图片路径
    /**
     * 参数类型必须是MultipartFile 参数名和前端必须一致  这样文件上传时,内部就会有文件对象
     * @param file
     * @param request
     * @return
     */
    @RequestMapping("/uploadImg.do")
    @ResponseBody
    public ResultDate uploadImg(MultipartFile file, HttpServletRequest request) throws IOException {

        String fileName = file.getOriginalFilename();
        //获取服务器文件路径
        String path = request.getServletContext().getRealPath("/upload_file");
        System.out.println(path);
        File parentFile = new File(path);
        if (!parentFile.exists()) {
            //不存在,就创建文件夹
            parentFile.mkdir();
        }
//        生成随机uuid,确保名称唯一
        String unique = UUID.randomUUID().toString();

        //获取文件后缀
        String ext = FilenameUtils.getExtension(fileName);

        String uniqueName = unique + "." + ext;
        System.out.println("唯一的文件名为:" + uniqueName);

        //上传文件到文件夹
        file.transferTo(new File(parentFile, uniqueName));
        return ResultDate.ok("upload_file/" + uniqueName);
    }
  1. 编写前端页面
<h1>文件上传回显</h1>
<!--文件上传回显,使用ajax提交,form不需要指定其他内容
-->
<form id="formData">
    文件上传:<input type="file" name="file"/>
    <input type="button" value="上传图片" onclick="uploadFile()"/>
</form>
<img src="" id="img" width="150" height="250"/>

<script src="js/jquery-1.8.3.min.js"></script>
<script type="application/javascript">
        function uploadFile(){
            //首先获得表单对象
            // $("#formData")[0] 将jquery对象转换为js对象
            var formData = new FormData($("#formData")[0]);

            $.ajax({
                url:"/uploadImg.do",
                type:"POST",
                data:formData,
                async:false,
                cache:false,
                contentType: false,
                processData: false,
                success:function (data) {
                    if (data.code === 200){
                        $("#img").attr("src",data.data);
                    }
                },
                error:function () {
                    alert("上传文件失败");
                }
            })
        }
</script>

12. 文件下载

  1. 前端页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<img src="/upload_file/0efe9235-1951-4d2e-9c4a-713d4287d211.jpg"/>
<a href="/download.do?fileName=0efe9235-1951-4d2e-9c4a-713d4287d211.jpg">下载</a>
</body>
</html>
  1. 编写Controller方法
    @RequestMapping("download.do")
    public void download(HttpServletRequest request, HttpServletResponse response, String fileName) throws IOException {
        // 获取文件夹的真实路径
        String realPath = request.getServletContext().getRealPath("/upload_file");

        // 拼接文件路径
        String filePath = realPath + "/" + fileName;
        System.out.println(filePath); // 打印文件路径,便于调试

        // 设置响应的内容类型为 application/octet-stream,这表示文件类型未知,浏览器会默认弹出下载对话框
        response.setContentType("application/octet-stream");

        // 设置响应头,告诉浏览器这是一个附件,并指定文件名
        response.addHeader("Content-Disposition", "attachment;filename=" + fileName);

        // 使用 Files.copy 方法将文件内容复制到响应的输出流中,实现文件下载
        Files.copy(Paths.get(filePath), response.getOutputStream());
    }

13. SSM整合(spring+springmvc+mybatis)

之前学习过spring和mybatis的依赖,现在整合继续mvc,其实差别不大,直接将配置文件复制过来即可,只是有个别地方不同。

  1. 创建JavaWeb项目(也就是Maven项目,选择webapp)

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>ssm</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
<!--      springmvc及其核心依赖-->
<!--      因为包含了context 所以可以不用导入-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>5.3.33</version>
      </dependency>
      <!--        spring核心依赖-->
<!--      <dependency>-->
<!--        <groupId>org.springframework</groupId>-->
<!--        <artifactId>spring-context</artifactId>-->
<!--        <version>5.3.33</version>-->
<!--      </dependency>-->
      <!--        mysql数据库驱动-->
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
      </dependency>
      <!--        aop依赖-->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.33</version>
      </dependency>
      <!--        mybatis依赖-->
      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.14</version>
      </dependency>
      <!--        单元测试依赖-->
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
      </dependency>
      <!--        日志依赖-->
      <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
      </dependency>
      <!--        mybatis分页插件依赖-->
      <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>5.3.3</version>
      </dependency>
      <!--        alibaba数据池依赖-->
      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.8</version>
      </dependency>
      <!--        整合依赖-->
      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>1.3.1</version>
      </dependency>
      <!--        spring-jdbc依赖-->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.33</version>
      </dependency>

      <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
      </dependency>
      <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.17.0</version>
      </dependency>
      <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.3</version>
        <exclusions>
          <exclusion>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
          </exclusion>
        </exclusions>
      </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.17.2</version>
    </dependency>
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.18.26</version>
      </dependency>

  </dependencies>

  <build>
    <!--    在IDEA的MAVEN项目中,默认源代码(java)目录下的xml等资源文件并不在编译的时候y一块打包进classes文件夹,-->
    <!--    而是直接舍弃掉,为了解决这个问题,有两种方法-->
    <!--    方法1 将xml或者properties等配置文件放在resources下,并修改配置文件的代码,比如说注册xml文件的位置-->
    <!--    方法2 在maven中添加过滤,将java下的xml进行过 滤-->
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <!--          默认  新添加自定义则失效-->
          <include>*.xml</include>
          <!--          新添加 */代表一级目录 **/代表多级目录-->
          <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
      </resource>
    </resources>
  </build>
</project>

  1. 配置文件

spring和mybatis的配置文件有applicationContext.xmlmybatis-config.xml,还有数据源jdbc.properties,日志记录log4j.properties。mvc的配置文件则是springmvc.xml,还有个web.xml。直接复制即可。有所差别的是,在之前的spring项目中,我们使用的是单元测试,通过获取工厂容器,再通过容器获取对象来测试,现在整合mvc以后,我们需要将工厂容器web.xml中进行加载。第二个就是在applicationContext.xml中扫描了注解,springmvc.xml中也扫描了一次注解,这样子会导致重复扫描,所以也需要进行修改。

第一次修改:web.xml 增加监听器,加载spring工厂容器

  <!--配置环境参数,指定Spring配置文件所在目录-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>

  <!--配置Spring的ContextLoaderListener监听器,初始化Spring容器-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

第二次修改:applicationContext.xml 和 springmvc.xml 的扫描注解修改

<!--    扫描注解-->
<!--    为了防止与mvc扫描注解重复,扫描除了controller以外的所有注解-->
    <context:component-scan base-package="org.demo">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

<!-- =======================    springmvc.xml   =============== -->
    <!--    扫描注解-->
<!--    为了防止与spring主配置文件中扫描注解重复,只扫描controller的注解-->
    <context:component-scan base-package="org.demo.controller" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

接下来,给出全部配置文件。

  1. 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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--    扫描注解-->
<!--    为了防止与mvc扫描注解重复,扫描除了controller以外的所有注解-->
    <context:component-scan base-package="org.demo">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
<!--        启用aop自动代理-->
<!--    <aop:aspectj-autoproxy/>-->

<!--    1. 加载数据源配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

<!--    2. 创建数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="initialSize" value="${jdbc.initialSize}"/>
        <property name="maxActive" value="${jdbc.maxActive}"/>
        <property name="minIdle" value="${jdbc.minIdle}"/>
        <property name="maxWait" value="${jdbc.maxWait}"/>
        <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
        <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
    </bean>

<!--    3. 创建SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
<!--        这些配置,如果mybaitis-config.xml写了,就可以不用再写了-->
        <property name="typeAliasesPackage" value="org.demo.entity"/>
<!--        可以直接引用-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

<!--    4. 扫描mapper,使mapper插入容器,获得mapper代理-->
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--        此处是上面的工厂id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--            扫描mapper,产生代理对象,加入spring容器-->
        <property name="basePackage" value="org.demo.mapper"/>
    </bean>

<!--    5. 配置事务管理 相当于切面-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--            需要注入数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>


    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

  1. jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/book?useSSL=false
jdbc.username=root
jdbc.password=
jdbc.initialSize=5
jdbc.maxActive=20
jdbc.minIdle=3
jdbc.maxWait=0
jdbc.timeBetweenEvictionRunsMillis=0
jdbc.minEvictableIdleTimeMillis=30000
  1. log4j.properties
log4j.rootLogger=ERROR, stdout

log4j.logger.org.demo.mapper=DEBUG


log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
  1. 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加载,这里不再需要-->
<!--    <properties resource="jdbc.properties"/>-->
    
    <settings>
        <setting name="logImpl" value="LOG4J"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="cacheEnabled" value="true"/>
    </settings>

    <plugins>
<!--        插件拦截器的路径-->
        <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
    </plugins>

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



<!--    扫描注解-->
<!--    为了防止与spring主配置文件中扫描注解重复,只扫描controller的注解-->
    <context:component-scan base-package="org.demo.controller" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

<!--    mvc注解开发驱动,用来取代映射器,和适配器-->
    <mvc:annotation-driven/>

<!--    静态资源处理-->
    <mvc:resources mapping="/**" location="/"/>
<!--    视图解析器-->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--        用来拼接页面的前缀-->
        <property name="prefix" value="/"/>
<!--            后缀-->
        <property name="suffix" value=".html"/>
    </bean>
</beans>
  1. web.xml (注意标签顺序,顺序不对会报错)
<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <!--配置环境参数,指定Spring配置文件所在目录-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>

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


  <!--配置Spring的ContextLoaderListener监听器,初始化Spring容器-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
<!--    <url-pattern>*.do</url-pattern>-->
        <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>

配置完成功,可以启动项目测试,为了便于测试,编写了mapper层、service层、controller层等,具体代码不再给出,只演示具体结果。

ssm.jpg

至此,查看到成功运行后,SSM的整合配置完成。