第1章 SpringMVC 的基本概念

1.1关于三层架构和 MVC

三层架构

  1. 我们开发服务器端程序,一般都基于两种形式,一种C/S架构程序,一种B/S架构程序
  2. 使用Java语言基本上都是开发B/S架构的程序,B/S架构又分成了三层架构
  3. 三层架构
    1. 表现层:WEB层,用来和客户端进行数据交互的。表现层一般会采用MVC的设计模型
    2. 业务层:处理公司具体的业务逻辑的
    3. 持久层:用来操作数据库的

MVC模型

  1. MVC全名是Model View Controller 模型视图控制器,每个部分各司其职。
  2. Model:数据模型,JavaBean的类,用来进行数据封装。
  3. View:指JSP、HTML用来展示数据给用户
  4. Controller:用来接收用户的请求,整个流程的控制器。用来进行数据校验等。

1.2 SpringMVC 概述

SpringMVC 是什么

SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于 Spring FrameWork 的后续产品,已经融合在 Spring Web Flow 里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用 Spring 进行 WEB 开发时,可以选择使用 Spring的 Spring MVC 框架或集成其他 MVC 开发框架,如 Struts1(现在一般不用),Struts2 等。
SpringMVC 已经成为目前最主流的 MVC 框架之一,并且随着 Spring3.0 的发布,全面超越 Struts2,成为最优秀的 MVC 框架。
它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful 编程风格的请求。

SpringMVC 在三层架构的位置

位置:表现层

SpringMVC 的优势

  1. 清晰的角色划分:
    前端控制器(DispatcherServlet)
    请求到处理器映射(HandlerMapping)
    处理器适配器(HandlerAdapter)
    视图解析器(ViewResolver)
    处理器或页面控制器(Controller)
    验证器( Validator)
    命令对象(Command 请求参数绑定到的对象就叫命令对象)
    表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。
  2. 分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要。
  3. 由于命令对象就是一个 POJO,无需继承框架特定 API,可以使用命令对象直接作为业务对象。
  4. 和 Spring 其他框架无缝集成,是其它 Web 框架所不具备的。
  5. 可适配,通过 HandlerAdapter 可以支持任意的类作为处理器。
  6. 可定制性,HandlerMapping、ViewResolver 等能够非常简单的定制。
  7. 功能强大的数据验证、格式化、绑定机制。
  8. 利用 Spring 提供的 Mock 对象能够非常简单的进行 Web 层单元测试。
  9. 本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
  10. 强大的 JSP 标签库,使 JSP 编写更容易。
    还有比如RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配置支持等等。

SpringMVC 和 Struts2 的优略分析

共同点:

  • 它们都是表现层框架,都是基于 MVC 模型编写的。
  • 它们的底层都离不开原始 ServletAPI。
  • 它们处理请求的机制都是一个核心控制器。

区别:

  • Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
  • Spring MVC 是基于方法设计的,而 Struts2 是基于类,Struts2 每次执行都会创建一个动作类。所以 Spring MVC 会稍微比 Struts2 快些。
  • Spring MVC 使用更加简洁,同时还支持 JSR303, 处理 ajax 的请求更方便(JSR303 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们 JavaBean 的属性上面,就可以在需要校验的时候进行校验了。)
  • Struts2 的 OGNL 表达式使页面的开发效率相比 Spring MVC 更高些,但执行效率并没有比 JSTL 提升,尤其是 struts2 的表单标签,远没有 html 执行效率高。

第2章 SpringMVC 的入门

2.1 SpringMVC 的入门案例

  1. 创建Maven工程,引入需要的Maven坐标
    具体的坐标如下
    <?xml version="1.0" encoding="UTF-8"?>

    <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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>quickSpringMvc</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>quickSpringMvc Maven Webapp</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring.version>5.0.2.RELEASE</spring.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
    </dependency>

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

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

    </dependencies>
    <build>
    <finalName>quickSpringMvc</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
    <plugins>
    <plugin>
    <artifactId>maven-clean-plugin</artifactId>
    <version>3.1.0</version>
    </plugin>
    <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
    <plugin>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.0.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    </plugin>
    <plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.1</version>
    </plugin>
    <plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>3.2.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-install-plugin</artifactId>
    <version>2.5.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-deploy-plugin</artifactId>
    <version>2.8.2</version>
    </plugin>
    </plugins>
    </pluginManagement>
    </build>
    </project>

我们需要在WEB-INF目录下,创建一个名为pages的文件夹,创建一个名为success的jsp页面,作为跳转页面使用。

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>入门成功了</h3>
</body>
</html>

我们是SpringMvc框架,所以还需要在resources目录下创建一个名为springmvc.xml的springmvc的配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
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
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="com.liuyu"></context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 配置spring开启注解mvc的支持-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
  1. 配置核心的控制器(配置DispatcherServlet)
    在web.xml配置文件中核心控制器DispatcherServlet
    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd" >

    <web-app>
    <display-name>Archetype Created Web Application</display-name>
    <!--配置前端控制器-->
    <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>
    </web-app>

3.启动程序测试,点击超连接发现能正常跳转。说明我们的入门案例搭建成功。

2.2 入门案例的执行过程及原理分析

  1. 服务器启动,应用被加载。读取到 web.xml 中的配置创建 spring 容器并且初始化容器中的对象。
  2. 浏览器发送请求,被 DispatherServlet 捕获,该 Servlet 并不处理请求,而是把请求转发出去。转发的路径是根据请求 URL,匹配@RequestMapping 中的内容。
  3. 匹配到了后,执行对应方法。该方法有一个返回值。
  4. 根据方法的返回值,借助 InternalResourceViewResolver 找到对应的结果视图。
  5. 渲染结果视图,响应浏览器。

2.3 入门案例中涉及的组件

DispatcherServlet:前端控制器

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

HandlerMapping:处理器映射器

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

Handler:处理器

它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由Handler 对具体的用户请求进行处理。

HandlAdapter:处理器适配器

通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

View Resolver:视图解析器

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

View:视图

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

mvc:annotation-driven说明

在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。使 用 mvc:annotation-driven 自动加载 RequestMappingHandlerMapping (处理映射器) 和RequestMappingHandlerAdapter ( 处 理 适 配 器 ) , 可 用 在 SpringMVC.xml 配 置 文 件 中 使 用mvc:annotation-driven替代注解处理器和适配器的配置。
它就相当于在 xml 中配置了:

<!-- 上面的标签相当于 如下配置-->
<!-- Begin -->
<!-- HandlerMapping 处理器映射器-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
<!-- HandlerAdapter处理器适配器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean>
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"></bean>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<!-- HadnlerExceptionResolvers -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver"></bean>
<bean class="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver"></bean>
<bean class="org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver"></bean>
<!-- End -->

注意:一般开发中,我们都需要写上此标签(虽然从入门案例中看,我们不写也行,随着对该框架深入,该标签还有具体的使用场景)。
明确:我们只需要编写处理具体业务的控制器以及视图。

2.4 RequestMapping 注解

  1. RequestMapping注解的作用是建立请求URL和处理方法之间的对应关系

  2. RequestMapping注解可以作用在方法和类上
    作用在类上:第一级的访问目录
    作用在方法上:第二级的访问目录
    细节:路径可以不编写 / 表示应用的根目录开始
    细节:${ pageContext.request.contextPath }也可以省略不写,但是路径上不能写 /
    例如:

    @Controller
    @RequestMapping("/user") //作用在类上
    public class HelloController {
    @RequestMapping(path = "/hello") //作用在方法上
    public String sayHello(){
    System.out.println("hello SpringMvc");
    return "success";
    }
    }
  3. RequestMapping的属性
    path:
    指定请求路径的url
    value:
    value属性和path属性是一样的。如果只有参数,并且是value或者path,可以省略不写。例如:@RequestMapping("/hello")
    mthod:
    指定该方法的请求方式 例如: @RequestMapping(value = "/testRequestMapping",method = {RequestMethod.POST}),当有两个或以上参数时,value不能省略。
    params:
    用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的 key 和 value 必须和配置的一模一样。
    例如:

    params = {"accountName"},表示请求参数必须有 accountName
    params = {"moeny!100"},表示请求参数中 money 不能是 100
    params = {"moeny=100"},表示请求参数中 money 必须是 100

    headers:
    用于指定限制请求消息头的条件。即发送的请求中必须包含的请求头
    例如:

     @RequestMapping(value = "/testRequestMapping",headers = {"Accept"})
    public String testRequestMapping(){
    System.out.println("hello SpringMvc");
    return "success";
    }

    当发送请求时,必须包含名为Accept的请求头,则可以正常访问该方法,否则则不能访问。

    注意:以上四个属性只要出现 2 个或以上时,他们的关系是 的关系。

第3章 请求参数的绑定

3.1 绑定的机制

我们都知道,表单中请求参数都是基于 key=value 的。
SpringMVC 绑定请求参数的过程是通过把表单提交请求参数,作为控制器中方法参数进行绑定的。

3.2 支持的数据类型

基本类型参数: 包括基本类型和 String 类型
POJO 类型参数:包括实体类,以及关联的实体类
数组和集合类型参数:包括 List 结构和 Map 结构的集合(包括数组)
SpringMVC 绑定请求参数是自动实现的,但是要想使用,必须遵循使用要求。

3.3 使用要求

如果是基本类型或者 String 类型
要求我们的参数名称必须和控制器中方法的形参名称保持一致。(严格区分大小写)

如果是 POJO 类型,或者它的关联对象:
要求表单中参数名称和 POJO 类的属性名称保持一致。并且控制器方法的参数类型是 POJO 类型。

如果是集合类型,有两种方式:
第一种:
要求集合类型的请求参数必须在 POJO 中。在表单中请求参数名称要和 POJO 中集合属性名称相同。给 List 集合中的元素赋值,使用下标。给 Map 集合中的元素赋值,使用键值对。
第二种:
接收的请求参数是 json 格式数据。需要借助一个注解实现

注意: 它还可以实现一些数据类型自动转换。内置转换器全都在:org.springframework.core.convert.support 包下。有:

java.lang.Boolean -> java.lang.String : ObjectToStringConverter
java.lang.Character -> java.lang.Number : CharacterToNumberFactory
java.lang.Character -> java.lang.String : ObjectToStringConverter
java.lang.Enum -> java.lang.String : EnumToStringConverter
java.lang.Number -> java.lang.Character : NumberToCharacterConverter
java.lang.Number -> java.lang.Number : NumberToNumberConverterFactory
java.lang.Number -> java.lang.String : ObjectToStringConverter
java.lang.String -> java.lang.Boolean : StringToBooleanConverter
java.lang.String -> java.lang.Character : StringToCharacterConverter
java.lang.String -> java.lang.Enum : StringToEnumConverterFactory
java.lang.String -> java.lang.Number : StringToNumberConverterFactory
java.lang.String -> java.util.Locale : StringToLocaleConverter
java.lang.String -> java.util.Properties : StringToPropertiesConverter
java.lang.String -> java.util.UUID : StringToUUIDConverter
java.util.Locale -> java.lang.String : ObjectToStringConverter
java.util.Properties -> java.lang.String : PropertiesToStringConverter
java.util.UUID -> java.lang.String : ObjectToStringConverter
......

如遇特殊类型转换要求,需要我们自己编写自定义类型转换器

3.4 使用示例

基本类型和 String 类型作为参数

Jsp代码

<a href="/param/testParam?username=hello&password=1234">请求参数绑定</a>

控制器代码:

@Controller
@RequestMapping("/param")
public class paramController {
@RequestMapping("/testParam")
public String testParam(String username,String password){
System.out.println("执行了...");
System.out.println("用户名:"+username);
System.out.println("密码:"+password);
return "success";
}
}

运行程序,控制器会打印出请求参数,说明我们已经能正常接收参数。

POJO 类型作为参数

实体类代码:

public class User implements Serializable {
private String username;
private int age;
//此处省略get/Set方法以及toString方法
}
public class Account implements Serializable {
private String username;
private String password;
private Double money;
private User user;
//此处省略get/Set方法以及toString方法
}

jsp 代码:

<!--把数据封装到Account类中,并包含其它对象-->
<form action="/param/saveAccount" method="post">
姓名:<input type="text" name="username"/><br/>
密码:<input type="text" name="password"/><br/>
金额:<input type="text" name="money"/><br/>
用户的姓名:<input type="text" name="user.username"/><br/>
用户的年龄:<input type="text" name="user.age"/><br/>
<input type="submit" value="提交">
</form>

控制器代码:

@Controller
@RequestMapping("/param")
public class paramController {
@RequestMapping("/saveAccount")
public String saveAccount(Account account){
System.out.println("执行了...");
System.out.println("用户名:"+account.getUsername());
System.out.println("密码:"+account.getPassword());
System.out.println("金额:"+account.getMoney());
System.out.println("用户姓名:"+account.getUser().getUsername());
System.out.println("用户年龄:"+account.getUser().getAge());
return "success";
}
}

运行程序,我们可以表单中的属性打印出来。以及User对象的属性。

请求参数乱码问题
但是,我们发现,POST请求下,姓名文本框和用户的姓名文本框,输入英文后台能正常显示。一旦输入中文就会变成乱码。
我们需要在 web.xml 中配置一个过滤器

<!--配置解决中文乱码的过滤器-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

再次运行,输入中文,发现已经解决了乱码问题。

POJO 类中包含集合类型参数

修改Account代码:

public class Account implements Serializable {
private String username;
private String password;
private Double money;

private List<User> list;
private Map<String,User> map;
//此处省略get/Set方法以及toString方法
}

jsp代码

<!--把数据封装Account类中,类中存在List和Map的集合-->
<form action="/param/saveAccount" method="post">
姓名:<input type="text" name="username"/><br/>
密码:<input type="text" name="password"/><br/>
金额:<input type="text" name="money"/><br/>

<!--放到list集合中-->
用户的姓名1:<input type="text" name="list[0].username"/><br/>
用户的年龄1:<input type="text" name="list[0].age"/><br/>

用户的姓名2:<input type="text" name="list[1].username"/><br/>
用户的年龄2:<input type="text" name="list[1].age"/><br/>
<!--放到Map集合中-->
用户的姓名1:<input type="text" name="map[one].username"/><br/>
用户的年龄1:<input type="text" name="map[one].age"/><br/>
用户的姓名2:<input type="text" name="map[two].username"/><br/>
用户的年龄2:<input type="text" name="map[two].age"/><br/>

<input type="submit" value="提交">
</form>

控制器代码:

@Controller
@RequestMapping("/param")
public class paramController {
@RequestMapping("/saveAccount")
public String saveAccount(Account account){
System.out.println("执行了...");
System.out.println("Account:"+account);
System.out.println("list集合:"+account.getList());
System.out.println("map集合:"+account.getMap());
return "success";
}
}

运行测试,我们能正常输出,说明我们已经按照要求把数据封装到对应的对象和集合中。

3.5 自定义类型转换器

类型转换异常演示

我们修改User对象如下:

public class User implements Serializable {
private String username;
private int age;
private Date date;
//此处省略get/Set方法以及toString方法
}

jsp代码

<!--自定义类型转换器-->
<form action="/param/saveUser" method="post">
姓名:<input type="text" name="username"/><br/>
年龄:<input type="text" name="age"/><br/>
生日:<input type="text" name="date"/><br/>
<input type="submit" value="提交">
</form>

控制器代码:

@Controller
@RequestMapping("/param")
public class paramController {
/**
* 自定义类型转换器
* @param user
* @return
*/
@RequestMapping("/saveUser")
public String saveUser(User user){
System.out.println("执行了...");
System.out.println("user:"+user);
return "success";
}
}

我们依次输入姓名张三,年龄12,生日2020/02/07,点击提交,后台会输出user:User{username='张三', age=12, date=Fri Feb 07 00:00:00 CST 2020},程序正常运行。
当我们把日期改成2020-02-07,再次运行会发现程序报错了。

程序无法识别传入的格式,这个时候我们需要自己编写一个自定义转换器。

自定义类型转换器代码编写
第一步:定义一个类,实现 Converter 接口,该接口有两个泛型

public interface Converter<S, T> {//S:表示接受的类型,T:表示目标类型
/**
* 实现类型转换的方法
*/
@Nullable
T convert(S source);
}

编写转换器:

/**
* 把字符串转换成日期
*/
public class StringToDateConvert implements Converter<String, Date> {
/**
* @param s 传入进来的字符串
* @return
*/
@Override
public Date convert(String s) {
//判断
if (s == null) {
throw new RuntimeException("请传入数据");
}
SimpleDateFormat df = new SimpleDateFormat("yyyy-mm-dd");

try {
//把字符串转换成日期
return df.parse(s);
} catch (ParseException e) {
throw new RuntimeException("数据转换异常:" + e);
}
}
}

第二步:在 spring 配置文件中配置类型转换器。
spring 配置类型转换器的机制是,将自定义的转换器注册到类型转换服务中去。

修改resource目录下的springmvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
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
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="com.liuyu"></context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

<!--配置自定义类型转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.liuyu.utils.StringToDateConvert"/> <!--这里写自定义转换器的全路径名-->
</set>
</property>
</bean>

<!-- 配置spring开启注解mvc的支持, conversion-service使自定义转换器生效-->
<mvc:annotation-driven conversion-service="conversionService"/>
</beans>

启动程序测试,当我们把日期继续改成2020-02-07,发现程序又能正常输出打印了。

3.6 获取servlet原生API

SpringMVC 还支持使用原始 ServletAPI 对象作为控制器方法的参数。支持原始 ServletAPI 对象有:
HttpServletRequest
HttpServletResponse
HttpSession
java.security.Principal
Locale
InputStream
OutputStream
Reader
Writer
我们可以把上述对象,直接写在控制的方法参数中使用。

jsp 代码:

 <!-- 原始 ServletAPI 作为控制器参数 -->
<a href="param/testServlet">Servlet原生API</a>

控制器代码:

@Controller
@RequestMapping("/param")
public class paramController {
/**
* 获取原生servlet API
* @param request
* @param response
* @return
*/
@RequestMapping("/testServlet")
public String testServlet(HttpServletRequest request, HttpServletResponse response, HttpSession session){
System.out.println("执行了...");
System.out.println("request对象:"+request);
System.out.println("response对象:"+response);
System.out.println("session对象:"+session);
return "success";
}
}

运行程序,会输出对象的地址

request对象:[email protected]7627dc96
response:[email protected]3d8dbb57
session:[email protected]22cb9b6d

第4章 常用注解

4.1 RequestParam

使用说明

作用
把请求中指定名称的参数给控制器中的形参赋值。默认情况下,前台请求参数必须和后台接收的形参保持一致。如果前台请求参数名称和后台接收的形参名称不一致,可以使用此注解。

属性
value:请求参数中的名称。
required:请求参数中是否必须提供此参数。默认值:true。表示必须提供并且与@RequestParam参数中的name必须一致,如果不提供将报错。

使用示例

jsp 中的代码:

<!-- requestParams 注解的使用 -->
<a href="/anno/testRequestParam?username=哈哈">testRequestParam</a>

控制器中的代码:

/**
* requestParams 注解的使用
*/
@Controller
@RequestMapping("/anno")
public class AnnoController {
@RequestMapping("/testRequestParam")
public String testRequestParam(@RequestParam(name = "username") String name){
System.out.println("执行了");
System.out.println(name);
return "success";
}
}

4.2 RequestBody

使用说明

作用:
用于获取请求体内容。直接使用得到是 key=value&key=value…结构的数据。
get 请求方式不适用。

属性:
required:是否必须有请求体。默认值是:true。当取值为 true 时,get 请求方式会报错。如果取值为 false,get 请求得到是 null。

使用示例

因为get方式没有请求体,全部封装在超连接中。所以改用post方式,这里选择表单进行测试

post 请求 jsp 代码:

<!-- request body 注解 -->
<form action="anno/testRequestBody" method="post">
姓名:<input type="text" name="username"/><br/>
年龄:<input type="text" name="age"/><br/>
<input type="submit" value="提交">
</form>

控制器代码:

/**
* RequestBody 注解的使用
*/
@Controller
@RequestMapping("/anno")
public class AnnoController {
/**
* 获取到请求体的内容
* @param body
* @return
*/
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String body){ //如果不加RequestBody这个注解,程序会认为取 body这个值
System.out.println("执行了");
System.out.println(body);
return "success";
}
}

运行程序测试,输入姓名hehe,年龄12,会打印出username=heh&age=12;

4.3 PathVaribale

使用说明

作用:
用于绑定 url 中的占位符。例如:请求 url 中 /delete/{id},这个{id}就是 url 占位符。
url 支持占位符是 spring3.0 之后加入的。是 springmvc 支持 rest 风格 URL 的一个重要标志。

属性:
value(name):用于指定 url 中占位符名称。
required:是否必须提供占位符。

使用示例

jsp 代码:

<!-- PathVariable 注解 -->
<a href="anno/testPathVariable/1">testPathVariable</a>

控制器代码:

/**
* PathVariable 注解的使用
*/
@Controller
@RequestMapping("/anno")
public class AnnoController {
/**
* PathVariable 注解
* @return
*/
@RequestMapping("/testPathVariable/{sid}")
public String testPathVariable(@PathVariable(name = "sid") String id){
System.out.println("执行了");
System.out.println(id);
return "success";
}
}

运行程序,会输出写在超连接中的sid

REST 风格 URL

REST(英文:Representational State Transfer,简称 REST)描述了一个架构样式的网络系统,比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,他是 HTTP 规范的主要编写者之一。在目前主流的三种 Web 服务交互方案中,REST 相比于 SOAP(Simple Object Access protocol,简单对象访问协议)以及 XML-RPC 更加简单明了,无论是对 URL 的处理还是对 Payload 的编码,REST 都倾向于用更加简单轻量的方法设计和实现。值得注意的是 REST 并没有一个明确的标准,而更像是一种设计的风格。

它本身并没有什么实用性,其核心价值在于如何设计出符合 REST 风格的网络接口。

restful 的优点:
它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

restful 的特性:
资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。
它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个 URI(统一资源定位符)指向它,每种资源对应一个特定的 URI 。要获取这个资源,访问它的 URI 就可以,因此 URI 即为每一个资源的独一无二的识别符。

表现层(Representation)把资源具体呈现出来的形式,叫做它的表现层 (Representation)。
比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式。

状态转化(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。

HTTP 协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是 “表现层状态转化”。具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET 、POST 、PUT、DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT用来更新资源,DELETE 用来删除资源。

restful 的示例:
/account/1 HTTP GET : 得到 id = 1 的 account
/account/1 HTTP DELETE: 删除 id = 1 的 account
/account/1 HTTP PUT: 更新 id = 1 的 account
/account HTTP POST: 新增 account

4.4 RequestHeader

使用说明

作用:
用于获取请求消息头。

属性:
value:提供消息头名称
required:是否必须有此消息头

注:在实际开发中一般不怎么用。

使用示例

jsp 中代码:

<!-- RequestHeader 注解 -->
<a href="anno/testRequestHeader">获取请求消息头</a>

控制器中代码:

/**
* RequestHeader 注解的使用
*/
@Controller
@RequestMapping("/anno")
public class AnnoController {
/**
* 获取请求头的值
* @return
*/
@RequestMapping("/testRequestHeader")
public String testRequestHeader(@RequestHeader(value = "Accept") String header){
System.out.println("执行了");
System.out.println("获取请求头中Accept的值:"+header);
return "success";
}
}

执行程序,会输出text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9;

4.5 CookieValue

使用说明

作用:
用于把指定 cookie 名称的值传入控制器方法参数。

属性:
value:指定 cookie 的名称。
required:是否必须有此 cookie。

使用示例

jsp 中的代码:

<!-- CookieValue 注解 -->
<a href="anno/testCookieValue">获取cookie 的值</a>

控制器中的代码:

/**
* CookieValue 注解的使用
*/
@Controller
@RequestMapping("/anno")
public class AnnoController {
/**
* 获取cookie 的值
* @return
*/
@RequestMapping("/testCookieValue")
public String testCookieValue(@CookieValue(value = "JSESSIONID") String cookieValue){
System.out.println("执行了");
System.out.println(cookieValue);
return "success";
}
}

运行程序,会输出DDE11AC537B47ECA27F1D3620A595E1F;

4.6 ModelAttribute

使用说明

作用:
该注解是 SpringMVC4.3 版本以后新加入的。它可以用于修饰方法和参数。出现在方法上,表示当前方法会在控制器的方法执行之前,先执行。它可以修饰没有返回值的方法,也可
以修饰有具体返回值的方法。
出现在参数上,获取指定的数据给参数赋值。

属性:
value:用于获取数据的 key。key 可以是 POJO 的属性名称,也可以是 map 结构的 key。

应用场景:
当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。

例如:
我们在编辑一个用户时,用户有一个创建信息字段,该字段的值是不允许被修改的。在提交表单数据是肯定没有此字段的内容,一旦更新会把该字段内容置为 null,此时就可以使用此注解解决问题。

使用示例

ModelAttribute 修饰方法带返回值

需求: 修改用户信息,要求用户的生日使用数据库中的数据

jsp代码:

<!--ModelAttribute注解,有返回值-->
<form action="anno/testModelAttribute" method="post">
姓名:<input type="text" name="username"/><br/>
年龄:<input type="text" name="age"/><br/>
<input type="submit" value="提交">
</form>

控制器的代码:

/**
* ModelAttribute注解 有返回值
*/
@Controller
@RequestMapping("/anno")
public class AnnoController {
/**
* testModelAttribute 注解
* @return
*/
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user){
System.out.println("testModelAttribute执行了....");
System.out.println(user);
return "success";
}

/**
* 该方法会先执行
* @param username
* @return
*/
@ModelAttribute
public User showUser(String username){
System.out.println("showUser执行了....");
//通过用户名查询数据库(模拟)
User user=new User();
user.setUsername(username);
user.setAge(20);
user.setDate(new Date());//这里模拟从数据库中查询得到的
return user;
}
}

运行程序会输出:

showUser执行了....
testModelAttribute执行了....
User{username='赵四', age=20, date=Sat Feb 08 15:18:55 CST 2020}

ModelAttribute 修饰方法无返回值

需求: 修改用户信息,要求用户的生日使用数据库中的数据

jsp代码:

<!--ModelAttribute注解,无返回值-->
<form action="anno/testModelAttribute" method="post">
姓名:<input type="text" name="username"/><br/>
年龄:<input type="text" name="age"/><br/>
<input type="submit" value="提交">
</form>

控制器的代码:

 /**
* testModelAttribute 注解
* @return
*/
@RequestMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute(value = "abc") User user){
System.out.println("testModelAttribute执行了....");
System.out.println(user);
return "success";
}

/**
* 该方法会先执行
* @param username
* @param map
*/
@ModelAttribute
public void showUser(String username, Map<String,User> map){
System.out.println("showUser执行了....");
//通过用户名查询数据库(模拟)
User user=new User();
user.setUsername(username);
user.setAge(20);
user.setDate(new Date());//这里模拟从数据库中查询得到的
map.put("abc",user);
}
}

运行程序会输出:

showUser执行了....
testModelAttribute执行了....
User{username='王五', age=52, date=Sat Feb 08 15:26:04 CST 2020}

4.7 SessionAttribute

使用说明

作用
用于多次执行控制器方法间的参数共享。

属性
value:用于指定存入的属性名称
type:用于指定存入的数据类型。

使用示例

jsp 中的代码:
<!-- SessionAttribute 注解的使用 -->
<a href="anno/testSessionAttribute">testSessionAttribute</a>

执行成功后跳转的success.jsp页面代码

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>入门成功了</h3>
${requestScope.msg}<br> <!--显示美美-->
${sessionScope} <!--显示{msg=美美}-->
</body>
</html>

控制器中的代码:

/**
* requestParams 注解的使用
*/
@Controller
@RequestMapping("/anno")
@SessionAttributes(value = {"msg"}) //把msg=美美存入到session域对象中
public class AnnoController {
/**
* SessionAttribute 注解
* @return
*/
@RequestMapping("/testSessionAttribute")
public String testSessionAttribute(Model model){
System.out.println("testSessionAttribute执行了....");
//底层会存储到request域对象中
model.addAttribute("msg","美美");
return "success";
}
}

执行程序,访问成功页面会显示:

${requestScope.msg}<br>  <!--显示美美-->
${sessionScope} <!--显示{msg=美美}-->

到此,我们把msg=美美 已经存到session域中,@SessionAttribute注解的作用是:用于多次执行控制器方法间的参数共享。那么我们从session域中把它取出来。

jsp代码

<!--session域对象存值-->
<a href="anno/testSessionAttribute">testSessionAttribute</a>
<!--session域对象取值-->
<a href="anno/testSessionAttribute">getSessionAttribute</a>

控制器中的代码:

/**
* requestParams 注解的使用
*/
@Controller
@RequestMapping("/anno")
@SessionAttributes(value = {"msg"}) //把msg=美美存入到session域对象中
public class AnnoController {
/**
* SessionAttribute 注解
* @return
*/
@RequestMapping("/testSessionAttribute")
public String testSessionAttribute(Model model){
System.out.println("testSessionAttribute执行了....");
//底层会存储到request域对象中
model.addAttribute("msg","美美");
return "success";
}

/**
* Session取值
* @return
*/
@RequestMapping("/getSessionAttribute")
public String getSessionAttribute(ModelMap modelMap){
System.out.println("testSessionAttribute执行了....");
//底层会存储到request域对象中
String msg=(String) modelMap.get("msg");
System.out.println(msg);
return "success";
}
}

我们执行程序,先执行存值,后执行取值。取值同时,msg也会在控制台打印。那我们要清除session怎么办呢?

jsp代码

<!--session域对象存值-->
<a href="anno/testSessionAttribute">testSessionAttribute</a>
<!--session域对象取值-->
<a href="anno/testSessionAttribute">getSessionAttribute</a>
<!--session域对象删除-->
<a href="anno/testSessionAttribute">delSessionAttribute</a>

控制器中的代码:

/**
* requestParams 注解的使用
*/
@Controller
@RequestMapping("/anno")
@SessionAttributes(value = {"msg"}) //把msg=美美存入到session域对象中
public class AnnoController {
/**
* SessionAttribute 注解
* @return
*/
@RequestMapping("/testSessionAttribute")
public String testSessionAttribute(Model model){
System.out.println("testSessionAttribute执行了....");
//底层会存储到request域对象中
model.addAttribute("msg","美美");
return "success";
}

/**
* Session取值
* @return
*/
@RequestMapping("/getSessionAttribute")
public String getSessionAttribute(ModelMap modelMap){
System.out.println("testSessionAttribute执行了....");
//底层会存储到request域对象中
String msg=(String) modelMap.get("msg");
System.out.println(msg);
return "success";
}

/**
* 清除session
* @param status
* @return
*/
@RequestMapping("/delSessionAttribute")
public String delSessionAttribute(SessionStatus status){
System.out.println("testSessionAttribute执行了....");
status.setComplete();
return "success";
}
}

我们先点击testSessionAttribute存入域对象链接,然后点击getSessionAttribute获取域对象链接,发现域对象已经存入到session中。当我们在此点击delSessionAttribute删除域对象链接后,在进行点击getSessionAttribute发现域对象已经被删除了。

第5章 响应数据和结果视图

5.0 环境搭建

  1. 创建Maven工程,引入需要的Maven坐标
    具体的坐标如下
    <?xml version="1.0" encoding="UTF-8"?>

    <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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>springResponse</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>quickSpringMvc Maven Webapp</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring.version>5.0.2.RELEASE</spring.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
    </dependency>

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

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

    </dependencies>
    <build>
    <finalName>quickSpringMvc</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
    <plugins>
    <plugin>
    <artifactId>maven-clean-plugin</artifactId>
    <version>3.1.0</version>
    </plugin>
    <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
    <plugin>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.0.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    </plugin>
    <plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.1</version>
    </plugin>
    <plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>3.2.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-install-plugin</artifactId>
    <version>2.5.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-deploy-plugin</artifactId>
    <version>2.8.2</version>
    </plugin>
    </plugins>
    </pluginManagement>
    </build>
    </project>

我们需要在WEB-INF目录下,创建一个名为pages的文件夹,创建一个名为success的jsp页面,作为跳转页面使用。

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>执行成功了</h3>
</body>
</html>

创建一个实体对象

public class User implements Serializable {
private String username;
private int age;
private String password;
//此处省略get/Set方法以及toString方法
}

我们是SpringMvc框架,所以还需要在resources目录下创建一个名为springmvc.xml的springmvc的配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
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
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="com.liuyu"></context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 配置spring开启注解mvc的支持-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
  1. 配置核心的控制器(配置DispatcherServlet)
    web.xml配置文件中核心控制器DispatcherServlet
    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd" >

    <web-app>
    <display-name>Archetype Created Web Application</display-name>
    <!--配置前端控制器-->
    <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!--配置解决中文乱码的过滤器-->
    <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    </web-app>

到这里,我们环境就搭建完成了。

5.1 返回值分类

响应之返回值是String

发起请求的jsp代码:

<a href="/user/testString">testString</a>

请求成功,获取域对象jsp代码:

修改WEB-INF目录下,pages里面的success.jsp:
请求成功,跳转页面获取域对象,此处使用的是EL表达式获取

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" isErrorPage="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>执行成功了</h3>
${user.username}
${user.password}
</body>
</html>

控制器代码

@Controller
@RequestMapping("/user")
public class UserController {
/**
* 返回值类型是String
* @param model
* @return
*/
@RequestMapping("/testString")
public String testString(Model model){
System.out.println("testString方法执行了");
//模拟从数据库中查出User对象
User user=new User();
user.setUsername("美美");
user.setPassword("123");
user.setAge(20);
//放入到model对象中,存到request域对象中,然后转发到页面。从页面中取出来
model.addAttribute("user",user);
return "success";
}
}

响应之返回值是void

该方法存在一个默认情况,请看下面演示案例:

jsp代码:

<a href="/user/testVoid">testVoid</a>

控制器代码:

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

/**
* 返回值类型是void
* @param model
*/
@RequestMapping("/testVoid")
public void testVoid(Model model){
System.out.println("testVoid方法执行了");
}
}

当前我们的控制器代码是没有返回值,如果执行会有什么结果呢?

当我们点击请求链接时,会报一个404的错误,大概意思是找不到以下路径:/项目名/WEB-INF/pages/user/testVoid.jsp

仔细查看报错原因,发现存在一个默认情况。说明我们没有指定返回值的时候,如果请求路径是什么,它就会默认访问请求路径.jsp文件

事实上我们并不想这样,有两种解决方案,第一种是编写一个和请求路径一致的jsp页面,但是这种也不太好,我们看第二种。

请求转发方式

修改控制器代码:

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

/**
* 返回值类型是void
* @param request
* @param response
* 请求转发一次请求,不用编写项目的名称
*/
@RequestMapping("/testVoid")
public void testVoid(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//编写请求转发的程序
System.out.println("testVoid方法执行了");
//请求转发时候,不会执行视图解析器,所以我们需要写完整路径。不执行试图解析器,就不会自动跳转到 WEB-INF/pages目录下
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);
//一般转发程序执行完以后,如果后面有代码还会继续执行,如果不想后续代码执行,写一个return即可。
return;
}
}

重定向方式

webapp目录下,创建一个名为response的jsp文件:

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

控制器代码:

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

/**
* 返回值类型是void
* @param request
* @param response
* 重定向是两次请求,需要写项目名称
*/
@RequestMapping("/testVoid")
public void testVoid(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//编写请求转发的程序
System.out.println("testVoid方法执行了");
//重定向,即重新发送一个请求,所以不能直接访问WEB-INF下的目录
response.sendRedirect(request.getContextPath()+"/response.jsp");
return;
}
}

直接进行响应方式

控制器代码:

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

/**
* 返回值类型是void
* @param request
* @param response
* 重定向是两次请求,需要写项目名称
*/
@RequestMapping("/testVoid")
public void testVoid(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//编写请求转发的程序
System.out.println("testVoid方法执行了");
//如果是中文,需要设置中文乱码
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");

//直接会进行响应
response.getWriter().println("hello你好"); //这种情况页面不会跳转,会直接输出 hello你好
return;
}
}

响应之返回值是ModelAndView

ModelAndView 是 SpringMVC 为我们提供的一个对象,该对象也可以用作控制器方法的返回值。

请求链接jsp代码:

<a href="/user/testModelAndView">testModelAndView</a>

请求成功,跳转页面获取域对象,此处使用的是EL表达式获取

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" isErrorPage="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>执行成功了</h3>
${user.username}
${user.password}
</body>
</html>

控制器代码:

@Controller
@RequestMapping("/user")
public class UserController {
/**
* 返回值类型是ModelAndView
*
* @param model
* @return
*/
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(Model model) {
System.out.println("testModelAndView方法执行了");
//模拟从数据库中查出User对象
User user = new User();
user.setUsername("小张");
user.setPassword("456");
user.setAge(20);

//创建ModelAndView对象
ModelAndView mv = new ModelAndView();

//把User对象存储到mv对象中,也会把user对象存入到request对象
mv.addObject("user", user);

//跳转到哪个页面
mv.setViewName("success"); //ModelAndView 跳转页面会执行视图解析器。所以只需写文件名即可,此处是跳转到success.jsp页面

return mv;
}
}

5.2 转发和重定向

forward 转发

jsp代码:

<a href="/user/testForward">testForward</a></br>

控制器代码:

@Controller
@RequestMapping("/user")
public class UserController {
/**
* 使用关键字方式进行转发
* @param model
* @return
*/
@RequestMapping("/testForward")
public String testForward(Model model) {
System.out.println("testForward方法执行了");
//请求的转发
return "forward:/WEB-INF/pages/success.jsp"; //不能使用视图解析器,需要写完整路径
}
}

Redirect 重定向

jsp代码:

<a href="/user/testRedirect">testRedirect</a></br>

webapp目录下,创建一个名为redirect的jsp文件:

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

控制器代码:

@Controller
@RequestMapping("/user")
public class UserController {
/**
* 使用关键字方式进行重定向
* @param model
* @return
*/
@RequestMapping("/testRedirect")
public String testRedirect(Model model) {
System.out.println("testRedirect方法执行了");
//重定向
return "redirect:/redirect.jsp"; //WEB-INF/pages/success.jsp请求不到
}
}

注: 在使用关键字进行转发或者重定向时候,不需要写项目名,框架已经帮我们封装好了。

5.3 ResponseBody响应json数据

我们之前都是转发或者重定向跳转到jsp页面,然后在去做响应。现在有如下场景:页面发送ajax异步请求,后台需要把一些对象转换成json的字符串,在响应回去。

静态资源拦截问题

我们需要在webapp目录下新建一个名为js的文件夹,放入jquery.js。在webapp目录下新建一个名为responseBody.jsp的文件

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<%--引入js--%>
<script src="js/jquery.min.js"></script>

<script>
//页面加载,绑定单击事件
$(function () {
$("#btn").click(function () {
alert("hello")
})
})
</script>
</head>
<body>
<%--模拟异步请求--%>
<button id="btn">发送ajax请求</button>
</body>
</html>

我们启动程序,打开页面,点击按钮,发现没反应。检查代码也没发现错误的地方。

我们回想一下,我们在web.xml中配置了配置前端控制器(DispatcherServlet),发现拦截方式是斜杠,即<url-pattern>/</url-pattern>,代表所有资源都会拦截。当我们引入js文件,单击按钮时候,它也会请求服务器中的对应js文件。但是我们配置前端控制器(DispatcherServlet)会把我们静态资源全部拦截。

DispatcherServlet会拦截到所有的资源,导致一个问题就是静态资源(img、css、js)也会被拦截到,从而不能被使用。解决问题就是需要配置静态资源不进行拦截,在springmvc.xml配置文件添加如下配置:

<!-- 设置静态资源不过滤 -->
<mvc:resources location="/css/" mapping="/css/**"/> <!-- 样式 -->
<mvc:resources location="/images/" mapping="/images/**"/> <!-- 图片 -->
<mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript -->

再次尝试我们发现能正常弹窗了。

响应json数据之发送ajax请求

修改responseBody.jsp如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<%--引入js--%>
<script src="js/jquery.min.js"></script>

<script>
//页面加载,绑定单击事件
$(function(){
$("#btn").click(function(){
$.ajax({
//编写json格式,设置属性和值
url:"user/testAjax",
contentType:"application/json;charset=UTF-8",
data:'{"username":"hehe","password":"123","age":"26"}', //模拟一个标准的json数据
dataType:"json",
type:"post",
success:function (data) {
//data服务��端响应的json数据,进行解析

}
});
})
})
</script>
</head>
<body>
<%--模拟异步请求--%>
<button id="btn">发送ajax请求</button>
</body>
</html>

控制器代码

@Controller
@RequestMapping("/user")
public class UserController {
/**
* 模拟异步请求和响应
* @param body
* @return
*/
@RequestMapping("/testAjax")
public void testAjax(@RequestBody String body) { //@RequestBody获取请求体,我们的请求体是一个json字符串
System.out.println("testAjax方法执行了");
System.out.println(body);
}
}

运行程序,点击请求链接,我们会把请求体也就是json数据打印出来。

testAjax方法执行了
{"username":"hehe","password":"123","age":"26"}

响应json数据之响应json格式数据

我们拿到的是一个json字符串,那么我们能不能把它转换成javaBean对象呢?同时我们在把json字符串响应回去,客户端拿到响应结果可以做一些局部刷新、页面提示灯操作。

我们发过去的一个json字符串,如果json字符串的key 值和javaBean对象中属性名相同的话,Spring会自动把它json字符串封装到一个java对象中。但是Spring在做转换的时候需要使用到额外的一个租jar包。 json字符串和JavaBean对象互相转换的过程中,需要使用jackson的jar包
打开pom文件,添加jackson的坐标。

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

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

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>

修改控制器代码:

@Controller
@RequestMapping("/user")
public class UserController {
/**
* 模拟异步请求和响应
* @param body
* @return
*/
@RequestMapping("/testAjax")
public @ResponseBody User testAjax(@RequestBody User user) { //@RequestBody获取请求体,我们的请求体是一个字符串
System.out.println("testAjax方法执行了");
//客户端发送ajax请求,传的是json字符串,spring通过jackson把字符串封装到java对象中
System.out.println(user);
//假装模拟查询数据库
user.setUsername("haha");
user.setAge(18);
user.setPassword("123");

//做响应. 我们返回的是一个java对象,但是前台需要一个json类型的数据。所以我们需要加一个注解:@ResponseBody
return user;
}
}

完善jsp:代码:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<%--引入js--%>
<script src="js/jquery.min.js"></script>

<script>
//页面加载,绑定单击事件
$(function(){
$("#btn").click(function(){
$.ajax({
//编写json格式,设置属性和值
url:"user/testAjax",
contentType:"application/json;charset=UTF-8",
data:'{"username":"hehe","password":"123","age":"26"}', //模拟一个标准的json数据
dataType:"json",
type:"post",
success:function (data) {
//data服务器端响应的json数据,进行解析
alert(data);
alert(data.username);
alert(data.age);
alert(data.password);
}
});
})
})
</script>
</head>
<body>
<%--模拟异步请求--%>
<button id="btn">发送ajax请求</button>
</body>
</html>

运行程序,发现能接受ajax异步请求的数据,同时也能把模拟的数据通过json方式返回到前台页面。

第6章 SpringMVC 实现文件上传

6.1 文件上传的回顾

文件上传的必要前提

  1. form 表单的 enctype 取值必须是:multipart/form-data,(默认值是:application/x-www-form-urlencoded)。enctype:是表单请求正文的类型
  2. method 属性取值必须是 Post
  3. 提供一个文件选择域<input type="file" />

文件上传的原理分析

当 form 表单的 enctype 取值不是默认值后,request.getParameter()将失效。
enctype=”application/x-www-form-urlencoded”时,form 表单的正文内容是:
key=value&key=value&key=value

当 form 表单的 enctype 取值为 Mutilpart/form-data 时,请求正文内容就变成:每一部分都是 MIME 类型描述的正文
—————————–7de1a433602ac 分界符
Content-Disposition: form-data; name=”userName” 协议头
aaa 协议的正文
—————————–7de1a433602ac
Content-Disposition: form-data; name=”file”;
filename=”C:\Users\zhy\Desktop\fileupload_demofile\b.txt”
Content-Type: text/plain 协议的类型(MIME 类型)
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
—————————–7de1a433602ac–

借助第三方组件实现文件上传

使用 Commons-fileupload 组件实现文件上传,需要导入该组件相应的支撑 jar 包:Commons-fileupload 和commons-io。commons-io 不属于文件上传组件的开发 jar 文件,但Commons-fileupload 组件从 1.1 版本开始,它工作时需要 commons-io 包的支持。

6.2 springmvc 传统方式的文件上传

传统方式的文件上传,指的是我们上传的文件和访问的应用存在于同一台服务器上。并且上传完成之后,浏览器可能跳转。

搭建环境

  1. 创建Maven工程,引入需要的Maven坐标
    具体的坐标如下
    <?xml version="1.0" encoding="UTF-8"?>

    <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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>SpringMvcFileUpload</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>quickSpringMvc Maven Webapp</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring.version>5.0.2.RELEASE</spring.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
    </dependency>

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

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

    <!--文件上传需要的jar包-->
    <dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
    </dependency>
    <dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
    </dependency>
    </dependencies>
    <build>
    <finalName>SpringMvcFileUpload</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
    <plugins>
    <plugin>
    <artifactId>maven-clean-plugin</artifactId>
    <version>3.1.0</version>
    </plugin>
    <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
    <plugin>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.0.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    </plugin>
    <plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.1</version>
    </plugin>
    <plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>3.2.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-install-plugin</artifactId>
    <version>2.5.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-deploy-plugin</artifactId>
    <version>2.8.2</version>
    </plugin>
    </plugins>
    </pluginManagement>
    </build>
    </project>

我们需要在WEB-INF目录下,创建一个名为pages的文件夹,创建一个名为success的jsp页面,作为跳转页面使用。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>文件上传成功</h3>
</body>
</html>

我们需要在WEB-INF目录下,创建css,js,images等文件夹

我们是SpringMvc框架,所以还需要在resources目录下创建一个名为springmvc.xml的springmvc的配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
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
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="com.liuyu"></context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

<!--前端控制器,哪些静态资源不拦截-->
<mvc:resources location="/css/" mapping="/css/**"/>
<mvc:resources location="/images/" mapping="/images/**"/>
<mvc:resources location="/js/" mapping="/js/**"/>


<!-- 配置spring开启注解mvc的支持-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
  1. 配置核心的控制器(配置DispatcherServlet)
    web.xml配置文件中核心控制器DispatcherServlet
    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd" >

    <web-app>
    <display-name>Archetype Created Web Application</display-name>
    <!--配置前端控制器-->
    <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!--配置解决中文乱码的过滤器-->
    <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    </web-app>
    到此,我们环境搭建完成。

修改index.jsp页面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>传统文件上传</h3>
<form action="/user/fileupload1" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"/></br>
<input type="submit" value="上传">
</form>
</body>
</html>

控制器代码

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

/**
* 传统文件上传
*
* @return
*/
@RequestMapping("/fileupload1")
public String fileUpload1(HttpServletRequest request) throws Exception {
System.out.println("传统文件上传...");
//使用fileupload组件完成文件上传
//上传位置
String path = request.getSession().getServletContext().getRealPath("/uploads/");

//判断,该路径是否存在
File file = new File(path);
if (!file.exists()) {
//不存在,创建文件夹
file.mkdirs();
}

//解析request对象,获取文件上传项
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
//解析request
List<FileItem> itemList = upload.parseRequest(request);

//遍历上传项
for (FileItem item : itemList) {
//进行判断,当前item对象是否是上传文件项
if (item.isFormField()) {
//为true,说明是一个普通表单项

} else {
//说明上传文件项
//获取上传文件名称
String fileName = item.getName();
//把文件名称设置成唯一值,uuid
String uuid = UUID.randomUUID().toString().replace("-", ""); //uuid 带横线,需要替换成空串
fileName=uuid+"_"+fileName;
//完成文件上传
item.write(new File(path, fileName));
//删除临时文件,大于10kb会产生临时文件,上传完会进行删除。如果小于10kb 会在内存中进行
item.delete();
}
}
return "success";
}
}

上传后,在target目录下,找到对应项目名称,即可看到上传的文件。

6.3 Springmvc的文件上传

SpringMvc文件上传原理:

使用springmvc方式进行文件上传,需要在resource目录下,springmvc.xml中配置一个文件解析器

<!--配置文件解析器-->
<!--id名称不允许修改,必须是这个-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485760"/> <!--10m-->
</bean>

index.jsp代码

<h3>SpringMvc文件上传</h3>
<form action="/user/fileupload2" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"/></br>
<input type="submit" value="上传">
</form>

控制器代码:

@Controller
@RequestMapping("/user")
public class UserController {
/**
* SpringMvc文件上传
*
* @return
*/
@RequestMapping("/fileupload2")
public String fileUpload2(HttpServletRequest request, MultipartFile upload) throws Exception {
System.out.println("SpringMvc文件上传...");
//使用fileupload组件完成文件上传
//上传位置
String path = request.getSession().getServletContext().getRealPath("/uploads/");

//判断,该路径是否存在
File file = new File(path);
if (!file.exists()) {
//不存在,创建文件夹
file.mkdirs();
}
//说明上传文件项
//获取上传文件名称
String fileName = upload.getOriginalFilename();//拿到文件传统名称

//把文件名称设置成唯一值,uuid
String uuid = UUID.randomUUID().toString().replace("-", ""); //uuid 带横线,需要替换成空串
fileName = uuid + "_" + fileName;
//完成文件上传
upload.transferTo(new File(path, fileName));
return "success";
}
}

上传后,在target目录下,找到对应项目名称,即可看到上传的文件。

6.4 Springmvc跨服务器方式的文件上传

分服务器的目的
在实际开发中,我们会有很多处理不同功能的服务器。例如:
应用服务器:负责部署我们的应用
数据库服务器:运行我们的数据库
缓存和消息服务器:负责处理大并发访问的缓存和消息
文件服务器:负责存储用户上传文件的服务器。
(注意:此处说的不是服务器集群)
分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。

那么在做测试时候,我们需要搭建一台图片服务器。为了方便演示这里搭建两个Tomcat服务器。分别是Springmvcfileupload。springmvc我们已经搭建完成了,这里需要搭建图片服务器环境,搭建完成后放到fileuploadTomcat服务器中。

创建Maven工程,修改webapp目录下的index.jsp:

<html>
<body>
<h2>Hello fileupload Server</h2>
</body>
</html>

这个项目部署到fileupload这个tomcat中,运行程序,在target目录下,进入以项目名称命名的文件夹,创建一个uploads的文件夹。一会把上传的文件存储到这里。到这里,我们的图片服务器搭建完成。

现在我们回到文件上传这个工程中,不是刚才创建的图片服务器工程。

添加pom依赖:

<!--跨服务器上传需要的jar包-->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
<version>1.18.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.18.1</version>
</dependency>

修改index.jsp:

<h3>SpringMvc跨服务器文件上传</h3>
<form action="/user/fileupload3" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"/></br>
<input type="submit" value="上传">
</form>

控制器代码:

@Controller
@RequestMapping("/user")
public class UserController {
/**
* SpringMvc跨服务器文件上传
*
* @return
*/
@RequestMapping("/fileupload3")
public String fileUpload3(MultipartFile upload) throws Exception {
System.out.println("SpringMvc跨服务器文件上传...");
//定义上传文件服务器路径
String path="http://localhost:9090/uploads/"; //图片服务器访问路径

//说明上传文件项
//获取上传文件名称
String fileName = upload.getOriginalFilename();//拿到文件传统名称

//把文件名称设置成唯一值,uuid
String uuid = UUID.randomUUID().toString().replace("-", ""); //uuid 带横线,需要替换成空串
fileName = uuid + "_" + fileName;
//创建客户端对象
Client client=Client.create();
//和图片服务器进行连接
WebResource webResource = client.resource(path + fileName); //拿到web资源
//上传文件
webResource.put(upload.getBytes());
return "success";
}
}

当我们测试上传时候,开了两个tomcat,一个作为应用服务器,一个作为文件服务器.当上传图片后出现returned a response status of 403 Forbidden的异常。

问题原因:
Tomcat默认只读,所以写入图片时被拒绝。

解决办法:
在tomcat的安装路径下找到conf目录下的web.xml,添加如下红框中的代码开启文件读写权限配置。

<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

再次尝试上传,发现能够正常上传成功。

第7章 SpringMVC 中的异常处理

7.1 异常处理的思路

系统中异常包括两类:预期异常和运行时异常 RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。
系统的 dao、service、controller 出现都通过 throws Exception 向上抛出,最后由 springmvc 前端控制器交由异常处理器进行异常处理,如下图:

7.2 环境搭建

  1. 创建Maven工程,引入需要的Maven坐标
    具体的坐标如下
    <?xml version="1.0" encoding="UTF-8"?>

    <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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>SpringException</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>SpringException Maven Webapp</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring.version>5.0.2.RELEASE</spring.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
    </dependency>

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

    <dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.0</version>
    <scope>provided</scope>
    </dependency>
    </dependencies>

    <build>
    <finalName>SpringException</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
    <plugins>
    <plugin>
    <artifactId>maven-clean-plugin</artifactId>
    <version>3.1.0</version>
    </plugin>
    <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
    <plugin>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.0.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    </plugin>
    <plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.1</version>
    </plugin>
    <plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>3.2.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-install-plugin</artifactId>
    <version>2.5.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-deploy-plugin</artifactId>
    <version>2.8.2</version>
    </plugin>
    </plugins>
    </pluginManagement>
    </build>
    </project>

我们需要在WEB-INF目录下,创建一个名为pages的文件夹,创建一个名为success的jsp页面,作为跳转页面使用。

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

我们是SpringMvc框架,所以还需要在resources目录下创建一个名为springmvc.xml的springmvc的配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
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
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="com.liuyu"></context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

<!--前端控制器,哪些静态资源不拦截-->
<mvc:resources location="/css/" mapping="/css/**"/>
<mvc:resources location="/images/" mapping="/images/**"/>
<mvc:resources location="/js/" mapping="/js/**"/>


<!-- 配置spring开启注解mvc的支持-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
  1. 配置核心的控制器(配置DispatcherServlet)
    web.xml配置文件中核心控制器DispatcherServlet
    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd" >

    <web-app>
    <display-name>Archetype Created Web Application</display-name>
    <!--配置前端控制器-->
    <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!--配置解决中文乱码的过滤器-->
    <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    </web-app>
    到此,我们环境搭建完成。

7.3 程序异常演示

修改inde.jsp文件:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>异常处理</h3>
<a href="user/testException">异常处理</a>
</body>
</html>

控制器代码:

@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testException")
public String testException(HttpServletRequest request) throws Exception {
System.out.println("testException执行了...");
int i=10/0; //模拟异常,并往前抛
return "success";
}
}

我们模拟了一个异常,抛给了前端控制器,这个时候我们没有配置异常处理器,所以前端控制器会把错误信息抛到浏览器页面上。这是一种非常不友好的默认方式。

7.4 异常处理

处理异常分以下几步:
1.编写自定义异常类(做提示信息的)

/**
* 自定义异常类
*/
public class SysException extends Exception {

//存储提示信息
private String getMessage;

public String getGetMessage() {
return getMessage;
}

public void setGetMessage(String getMessage) {
this.getMessage = getMessage;
}

public SysException(String message, String getMessage) {
this.getMessage = getMessage;
}
}

2.编写异常处理器

/**
* 异常处理器
*/
public class SysExceptionResolver implements HandlerExceptionResolver {
/**
* 处理异常逻辑
* @param httpServletRequest
* @param httpServletResponse
* @param o
* @param e
* @return
*/
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
//获取到异常对象
SysException exception=null;
//判断异常是否为我们自定义异常
if (e instanceof SysException){
//是
exception=(SysException)e;
}else {
//否
exception=new SysException("系统正在维护中...");
}
//创建ModelAndView对象
ModelAndView mv=new ModelAndView();
mv.addObject("errorMsg",exception.getGetMessage());
mv.setViewName("error");
return mv;
}
}

3.配置异常处理器(跳转提示)
resources目录下,修改springmvc.xml:

<!--配置异常处理器-->
<bean id="sysExceptionResolver" class="com.liuyu.exception.SysExceptionResolver"/>

我们需要在WEB-INF目录下,pages的文件夹下,创建一个名为error的jsp页面,作为异常跳转页面使用。

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${errorMsg} <!--返回ModelAndView会自动放入request域对象中-->
</body>
</html>

我们再一次运行程序,发现页面不会报错了。会提示之前定义的错误信息:
查询所有用户出现错误

第8章 SpringMVC 中的拦截器

8.1 拦截器的作用

Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。用户可以自己定义一些拦截器来实现特定的功能。
谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
说到这里,可能大家脑海中有了一个疑问,这不是过滤器吗?是的它和过滤器是有几分相似,但是也有区别,接下来我们就来说说他们的区别:

过滤器servlet 规范中的一部分,任何 java web 工程都可以使用。
拦截器SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
过滤器url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。
拦截器 它是只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦截的。
它也是 AOP 思想的具体应用。
我们要想自定义拦截器, 要求必须实现:HandlerInterceptor 接口。

8.2 自定义拦截器的步骤

PreHandle

PreHandle:预处理,controller方法执行前执行的方法

环境搭建

  1. 创建Maven工程,引入需要的Maven坐标
    具体的坐标如下
    <?xml version="1.0" encoding="UTF-8"?>

    <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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>SpringException</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>SpringException Maven Webapp</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring.version>5.0.2.RELEASE</spring.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
    </dependency>

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

    <dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.0</version>
    <scope>provided</scope>
    </dependency>
    </dependencies>

    <build>
    <finalName>SpringException</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
    <plugins>
    <plugin>
    <artifactId>maven-clean-plugin</artifactId>
    <version>3.1.0</version>
    </plugin>
    <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
    <plugin>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.0.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    </plugin>
    <plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.1</version>
    </plugin>
    <plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>3.2.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-install-plugin</artifactId>
    <version>2.5.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-deploy-plugin</artifactId>
    <version>2.8.2</version>
    </plugin>
    </plugins>
    </pluginManagement>
    </build>
    </project>

我们需要在WEB-INF目录下,创建一个名为pages的文件夹,创建一个名为success的jsp页面,作为跳转页面使用。

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

我们是SpringMvc框架,所以还需要在resources目录下创建一个名为springmvc.xml的springmvc的配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
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
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="com.liuyu"></context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

<!--前端控制器,哪些静态资源不拦截-->
<mvc:resources location="/css/" mapping="/css/**"/>
<mvc:resources location="/images/" mapping="/images/**"/>
<mvc:resources location="/js/" mapping="/js/**"/>


<!-- 配置spring开启注解mvc的支持-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
  1. 配置核心的控制器(配置DispatcherServlet)
    web.xml配置文件中核心控制器DispatcherServlet
    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd" >

    <web-app>
    <display-name>Archetype Created Web Application</display-name>
    <!--配置前端控制器-->
    <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!--配置解决中文乱码的过滤器-->
    <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    </web-app>

修改index.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>拦截器</h3>
<a href="user/testInterceptor">拦截器</a>
</body>
</html>

控制器代码:

@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testInterceptor")
public String testInterceptor(HttpServletRequest request) {
System.out.println("testInterceptor执行了...");
return "success";
}
}

到此,我们环境搭建完成。

编写拦截器类,需要实现HandlerInterceptor接口,但是我们实现这个接口后,没有强制要求实现接口里面的方法。那是为什么呢?原因是JDK1.8 把定义接口中的方法都实现了,不用我们实现了。如果我们想用自己的实现方法,重写方法就可以了。

/**
* 自定义拦截器
*/
public class MyInterceptor1 implements HandlerInterceptor {
/**
* 预处理,controller方法执行前
* return true:放行,执行下一个拦截器。如果没有,执行controller中的方法
* return false:不放行,可以用request或response跳转到某个指定页面
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截器执行了");
return true;
}
}

修改WEB-INF下,pages目录下的success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>执行成功了</h3>
<%System.out.println("success.jsp执行了."); %>
</body>
</html>

springmvc目录下,配置拦截器

<!--配置拦截器-->
<mvc:interceptors>
<!--配置拦截器-->
<mvc:interceptor>
<!--要拦截的方法-->
<mvc:mapping path="/user/*"/>
<!-- 不要拦截的方法,两个配置一个即可
<mvc:exclude-mapping path=""/>-->

<!--配置拦截器对象-->
<bean class="com.liuyu.interceptor.MyInterceptor1"></bean>
</mvc:interceptor>
</mvc:interceptors>

运行程序,我们单击请求链接,控制台会输出

拦截器执行了
testInterceptor执行了...
success.jsp执行了.

同时,页面也跳转到了success.jsp这个页面。这个是拦截器放行情况,会执行controller,跳转页面。

那么如果不放行,不执行controller。并在拦截器里设置页面跳转该怎么做呢?

WEB-INF下,pages目录下新建一个名为error.jsp的文件:

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>错误页面</h3>
</body>
</html>

修改拦截器配置类:

/**
* 自定义拦截器
*/
public class MyInterceptor1 implements HandlerInterceptor {
/**
* 预处理,controller方法执行前
* return true:放行,执行下一个拦截器。如果没有,执行controller中的方法
* return false:不放行,可以用request或response跳转到某个指定页面
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截器执行了");
request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response); //跳转到指定错误页面
return false;
}
}

执行程序,会跳转到指定错误页面,controller也没有执行。控制台只会打出拦截器的输出信息:拦截器执行了;

PostHandle

PostHandle:后处理,controller方法执行后执行的方法
修改自定义拦截器代码:

/**
* 自定义拦截器
*/
public class MyInterceptor1 implements HandlerInterceptor {
/**
* 预处理,controller方法执行前
* return true:放行,执行下一个拦截器。如果没有,执行controller中的方法
* return false:不放行,可以用request或response跳转到某个指定页面
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("第一个拦截器执行了。前");
//request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
return true;
}

/**
* 后处理方法,controller方法执行后,success.jsp执行前
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
System.out.println("第一个拦截器执行了。后");
}
}

我们先执行preHandle方法,拦截器放行,执行对应的controller方法。controller执行后,success.jsp执行前,执行postHandle方法,这里设置跳转错误页面。就不会在跳转controller中设置的success.jsp页面。但是success.jsp中输出代码会正常执行。
跳转到error.jsp页面,控制台会输出:

拦截器执行了。前
testInterceptor执行了...
拦截器执行了。后
success.jsp执行了.

afterCompletion

afterCompletion:success.jsp页面执行后,该方法会执行

拦截器代码:

/**
* 自定义拦截器
*/
public class MyInterceptor1 implements HandlerInterceptor {
/**
* 预处理,controller方法执行前
* return true:放行,执行下一个拦截器。如果没有,执行controller中的方法
* return false:不放行,可以用request或response跳转到某个指定页面
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("第一个拦截器执行了。前");
//request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
return true;
}

/**
* 后处理方法,controller方法执行后,success.jsp执行前
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
System.out.println("第一个拦截器执行了。后");
}

/**
* success.jsp页面执行后,该方法会执行
* @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("第一个拦截器执行了。最后");
}
}

再次执行,会输出:

拦截器执行了。前
testInterceptor执行了...
拦截器执行了。后
success.jsp执行了.
拦截器执行了。最后

8.3 多个拦截器执行顺序

我们在新建第二个个拦截器类,模拟多个拦截器执行顺序:

/**
* 自定义拦截器
*/
public class MyInterceptor2 implements HandlerInterceptor {
/**
* 预处理,controller方法执行前
* return true:放行,执行下一个拦截器。如果没有,执行controller中的方法
* return false:不放行,可以用request或response跳转到某个指定页面
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("第二个拦截器执行了。前");
//request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
return true;
}

/**
* 后处理方法,controller方法执行后,success.jsp执行前
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
System.out.println("第二个拦截器执行了。后");
}

/**
* success.jsp页面执行后,该方法会执行
* @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("第二个拦截器执行了。最后");
}
}

执行程序,多个拦截器执行顺序如下:

第一个拦截器执行了。前
第二个拦截器执行了。前
testInterceptor执行了...
第二个拦截器执行了。后
第一个拦截器执行了。后
success.jsp执行了.
第二个拦截器执行了。最后
第一个拦截器执行了。最后

因为有两个拦截器,所以最开始先执行拦截器1的preHandle方法,通过。在执行拦截器2的preHandle方法并通过。开始执行controller方法。controller执行完,success执行前,开始执行拦截器2的postHandle方法,在执行拦截器1的postHandle方法。在执行success.jsp,在执行拦截器2的afterCompletion方法。最后执行拦截器1的afterCompletion方法

第9章 SSM整合

9.1 搭建整合环境

  1. 整合说明:SSM整合可以使用多种方式,咱们会选择XML + 注解的方式
  2. 整合的思路
    1. 先搭建整合的环境
    2. 先把Spring的配置搭建完成
    3. 再使用Spring整合SpringMVC框架
    4. 最后使用Spring整合MyBatis框架

创建数据库和表结构

create database ssm;
use ssm;
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);

创建一个maven项目,修改pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>

<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>SSM</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<name>SSM Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.version>5.0.2.RELEASE</spring.version>
<slf4j.version>1.6.6</slf4j.version>
<log4j.version>1.2.12</log4j.version>
<mysql.version>5.1.6</mysql.version>
<mybatis.version>3.4.5</mybatis.version>
</properties>

<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>


<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- log start -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- log end -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
</dependencies>

<build>
<finalName>SSM</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

补全maven目录结构,在main下创建java目录resource目录,java目录下创建包com.ssm。在ssm包下分别创建controller,dao,domain,servicedomain。在service目录下创建impl

controller层

/**
* 账户web
*/
public class AccountController {
}

service层

public interface AccountService {
//查询所有账户
public List<Account> findAll();

//保存账户信息
public void saveAccount(Account account);
}

service实现类

public class AccountServiceImpl implements AccountService {
@Override
public List<Account> findAll() {
System.out.println("业务层:查询所有账户");
return null;
}

@Override
public void saveAccount(Account account) {
System.out.println("业务层:保存所有账户");
}
}

dao层

/**
* 账户dao接口
*/
public interface AccountDao {
//查询所有账户
public List<Account> findAll();

//保存账户信息
public void saveAccount(Account account);

}

domain层

/**
* 账户
*/
public class Account implements Serializable {
private Integer id;
private String name;
private Double money;
//省略get、set、ToString方法
}

9.2 编写Spring框架

resource目录下,创建spring配置文件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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启注解扫描,要扫描的是service和dao层的注解,要忽略web层注解,因为web层让SpringMVC框架
去管理 -->
<context:component-scan base-package="com.ssm">
<!-- 配置哪些注解不扫描 -->
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>

resource目录下,创建日志输出文件log4j.properties:

# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=info, CONSOLE, LOGFILE

# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE

# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n

# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n

在service实现类中,添加@Service("accountService")注解,把Service交给spring管理(依赖注入)。

@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Override
public List<Account> findAll() {
System.out.println("业务层:查询所有账户");
return null;
}

@Override
public void saveAccount(Account account) {
System.out.println("业务层:保存所有账户");
}
}

创建测试类,测试service是否注入成功:

public class testSpring {

@Test
public void run1(){
//加载配置文件
ApplicationContext ac=new ClassPathXmlApplicationContext("classpath:application.xml");

//获取对象
AccountService as = (AccountService) ac.getBean("accountService");

//调用方法
as.findAll();
}
}

测试成功,打印业务层:查询所有账户;

9.3 编写SpringMvc框架

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

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

<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置前端控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--加载springmvc.xml配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value> <!--下一步创建springmvc.xml-->
</init-param>
<!--启动服务器,创建servlet-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<!--配置解决中文乱码的过滤器-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

resource下创建名为springmvc.xml的配置文件,编写配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
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
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启扫描controller的注解,别的不扫描 -->
<context:component-scan base-package="com.ssm">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- JSP文件所在的目录 -->
<property name="prefix" value="/WEB-INF/pages/"/>
<!-- 文件的后缀名 -->
<property name="suffix" value=".jsp"/>
</bean>
<!-- 设置静态资源不过滤 -->
<mvc:resources location="/css/" mapping="/css/**"/>
<mvc:resources location="/images/" mapping="/images/**"/>
<mvc:resources location="/js/" mapping="/js/**"/>
<!-- 开启对SpringMVC注解的支持 -->
<mvc:annotation-driven/>
</beans>

springmvc已经配置完成,我们需要验证是否配置成功。修改index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<a href="account/findAll">测试</a>
</body>
</html>

我们需要在WEB-INF目录下,创建一个名为pages的文件夹,创建一个名为list的jsp页面,作为测试跳转页面使用。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>查询了所有的账户信息</h3>
</body>
</html>

修改Accountcontroller

/**
* 账户web
*/
@Controller
@RequestMapping("/account")
public class AccountController {

@RequestMapping("/findAll")
public String findAll(){
System.out.println("表现层:查询所有的账户信息...");
return "list";
}
}

运行测试,单击请求链接,页面能正常跳转,控制台能正常输出表现层:查询所有的账户信息...。说明我们的springmvc配置成功。

9.4 Spring整合SpringMvc框架

正常情况下,我们需要把service注入到controller中,然后进行调用service。但是有个问题,我们虽然把service层加了注解,开启了注解扫描,把它交给spring管理。不过通过查看web.xml配置文件,发现程序启动时候只加载了springmvc的配置文件。并没有加载spring配置文件,导致注解扫描那些配置未生效,就无法加入到spring ioc容器中。controller层也就无法正常注入。

解决方案是:在启动服务时候,把springmvc和spring的配置都加载,spring配置加载了,开启的包扫描就会生效,就会把service放入容器中,controller也就能正常注入了。

web.xml中配置监听器:

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

<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- 配置Spring的监听器,默认只加载WEB-INF目录下的applicationContext.xml配置文件 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置加载类路径的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>

<!--配置前端控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--加载springmvc.xml配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--启动服务器,创建servlet-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<!--配置解决中文乱码的过滤器-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

到此,controller和service都已经放到容器中了,下面我们测试是否正确

修改controller代码,按类型自动注入service:

/**
* 账户web
*/
@Controller
@RequestMapping("/account")
public class AccountController {

@Autowired
private AccountService accountService;

@RequestMapping("/findAll")
public String findAll(){
System.out.println("表现层:查询所有的账户信息...");
//调用service方法
accountService.findAll();
return "list";
}
}

单击请求连接,页面正常跳转,控制台输出

表现层:查询所有的账户信息...
业务层:查询所有账户

这样就表示配置成功。

9.5 编写Mybatis框架

resource下创建名为SqlMapConfig.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>
<!--配置环境-->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<!-- 引入映射配置文件,这里使用的是注解 -->
<mappers>
<!-- <mapper resource="com/ssm/dao/xxx.xml"></mapper> 这是使用配置文件的配置方式-->
<!-- <mapper class="com.ssm.dao.AccountDao"/> 这是使用注解 的配置方式-->
<!-- 该包下所有的dao接口都可以使用 -->
<package name="com.ssm.dao"/>
</mappers>
</configuration>

修改Dao:

/**
* 账户dao接口
*/
public interface AccountDao {
//查询所有账户
@Select("select * from account")
public List<Account> findAll();

//保存账户信息
@Insert("insert into account (name,money) values (#{name},#{money})")
public void saveAccount(Account account);

}

编写测试类:

public class testMyBatis {
/**
* 测试查询
* @throws IOException
*/
@Test
public void run1() throws IOException {
//加载mybatis配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

//创建SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

//创建SqlSession对象
SqlSession session = factory.openSession();

//获取到代理对象(没有实现类,使用的是代理的方式)
AccountDao dao = session.getMapper(AccountDao.class);

//查询所有账户信息
List<Account> list = dao.findAll();
for (Account account:list){
System.out.println(account);
}
//关闭资源
session.close();
in.close();

}

/**
* 测试保存
* @throws IOException
*/
@Test
public void run2() throws IOException {
//加载mybatis配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

//创建SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

//创建SqlSession对象
SqlSession session = factory.openSession();

//获取到代理对象(没有实现类,使用的是代理的方式)
AccountDao dao = session.getMapper(AccountDao.class);

//保存
Account account=new Account();
account.setName("东凛");
account.setMoney(6666.0);
dao.saveAccount(account);

//提交事务 查询不需要提交事务,增删改都需要提交事务
session.commit();

//关闭资源
session.close();
in.close();

}
}

9.6 Spring整合Mybatis框架

获取mybatis的代理对象,存入到容器中。在service中注入Dao。把SqlMapConfig.xml配置文件中的内容配置到applicationContext.xml配置文件中

修改Spring的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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启注解扫描,要扫描的是service和dao层的注解,要忽略web层注解,因为web层让SpringMVC框架
去管理 -->
<context:component-scan base-package="com.ssm">
<!-- 配置哪些注解不扫描 -->
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<!--Spring整合MyBatis框架-->

<!-- 配置C3P0的连接池对象 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql:///ssm" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<!-- 配置SqlSession的工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置扫描dao的包,spring创建接口代理对象,存入到容器中 -->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.ssm.dao"/>
</bean>
</beans>

这时候我们就可以删除SqlMapConfig.xml这个配置文件了。

修改AccountDao,增加@Repository注解

/**
* 账户dao接口
*/
@Repository
public interface AccountDao {
//查询所有账户
@Select("select * from account")
public List<Account> findAll();

//保存账户信息
@Insert("insert into account (name,money) values (#{name},#{money})")
public void saveAccount(Account account);

}

修改Service实现类,注入accountDao:

@Service("accountService")
public class AccountServiceImpl implements AccountService {

@Autowired
private AccountDao accountDao;

@Override
public List<Account> findAll() {
System.out.println("业务层:查询所有账户");
return accountDao.findAll();
}

//保存需要事务,我们先测试查询
@Override
public void saveAccount(Account account) {
System.out.println("业务层:保存所有账户");
accountDao.saveAccount(account);
}
}

修改controller:

/**
* 账户web
*/
@Controller
@RequestMapping("/account")
public class AccountController {

@Autowired
private AccountService accountService;

@RequestMapping("/findAll")
public String findAll(Model model){
System.out.println("表现层:查询所有的账户信息...");
//调用service方法
List<Account> list = accountService.findAll();

model.addAttribute("list",list);
return "list";
}
}

我们把查出的数据需要做个展示,之前已经设置转发到list.jsp页面了,但是我们需要修改一下list.jsp,便于展示查询的数据。
使用jstl遍历数据:

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>查询了所有的账户信息</h3>
${list}
<c:forEach items="${list}" var="account">
${account.name}
${account.money}
</c:forEach>
</body>
</html>

重启程序,点击页面查询所有的链接,页面会显示查出的数据。

9.7 Spring整合Mybatis框架事务配置

保存方法需要我们手动事务管理,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"
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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启注解扫描,要扫描的是service和dao层的注解,要忽略web层注解,因为web层让SpringMVC框架
去管理 -->
<context:component-scan base-package="com.ssm">
<!-- 配置哪些注解不扫描 -->
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<!--Spring整合MyBatis框架-->
<!-- 配置C3P0的连接池对象 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://118.89.216.120:3306/ssm"/>
<property name="username" value="root"/>
<property name="password" value="562644"/>
</bean>
<!-- 配置SqlSession的工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置扫描dao的包,spring创建接口代理对象,存入到容器中 -->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.ssm.dao"/>
</bean>

<!--配置Spring的声明式事务管理-->
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="find/*" read-only="true"/>
<tx:method name="*" isolation="DEFAULT"></tx:method>
</tx:attributes>
</tx:advice>
<!--配置AOP增强-->
<aop:config>
<aop:advisor advice-ref="txAdvice"
pointcut="execution(* com.ssm.service.impl.*ServiceImpl.*(..))"></aop:advisor>
</aop:config>
</beans>

修改index.jsp,添加一个表单录入:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<a href="account/findAll">测试</a>

<form action="/account/save" method="post">
姓名:<input type="text" name="name"><br/>
金额:<input type="text" name="money"><br/>
<input type="submit" name=保存>
</form>
</body>
</html>

编写保存controller方法:

/**
* 账户web
*/
@Controller
@RequestMapping("/account")
public class AccountController {
@RequestMapping("/save")
public void save(Account account, HttpServletRequest request,HttpServletResponse response) throws IOException {
System.out.println("保存方法...");
//调用service方法
accountService.saveAccount(account);
//重定向,保存执行完,重定向到findAll方法 把保存数据查询出来
response.sendRedirect(request.getContextPath()+"/account/findAll");
return;
}
}

填写表单数据,保存成功后,会把所有保存的数据展示出来。
SSM整合到这里就结束了。