Spring MVC教程——检视阅读

参考

Spring MVC教程——一点——蓝本

Spring MVC教程——c语言中午网——3.0版本太老了

Spring MVC教程——易百——4.0版本不是通过maven创建的

Spring MVC教程——javaschool纯理论

Spring MVC应用——javaschool纯理论

Spring MVC学习总结

Spring MVC 4.2.4.RELEASE 中文文档——基础学习后看

SpringMVC面试题

目前大多数使用的是spring4.x.

springboot基于 Spring Framework 5.0 on JDK 8 & 9


Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。

  • 基于Spring MVC框架:spring-framework-5.0.2.RELEASE

什么是MVC

MVC是一个架构,或者说是一个设计模式,它就是强制性使应用程序的输入,处理和输出分开。将一个应用程序分为三个部分:Model,View,Controller。

原理图 :

Model模型:负责完成业务逻辑:由JavaBean构成,在MVC的三个部件中,模型拥有最多的处理任务。例如它可能用像EJB和javabean这样的构件对象来处理数据库。由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。

View视图:就是负责跟用户交互的界面。一般就是由HTML,css元素组成的界面,当然现在还有一些像js,ajax,flex一些也都属于视图层。 在视图层里没有真正的处理发生,只负责数据输出,并允许用户操纵的方式。MVC能为应用程序处理很多不同的视图。

Controller控制器:负责接收请求—>调用模型—>根据结果派发页面并经过模型处理返回相应数据。

MVC的优点

1、分工明确:使用MVC可以把数据库开发,程序业务逻辑开发,页面开发分开,每一层都具有相同的特征,方便以后的代码维护。它使程序员可以集中精力于各自的模型开发领域。

2、松耦合:视图层和业务层分离,这样就允许更改视图层代码而不用重新编译模型和控制器代码,同样,一个应用的业务流程或者业务规则的改变只需要改动MVC的模型层即可。因为模型与控制器和视图相分离,所以很容易改变应用程序的数据层和业务规则。

3、复用性高(利于各层逻辑的复用):像多个视图能够共享一个模型,不论你视图层是用flash界面或是wap界面,用一个模型就能处理他们。将数据和业务规则从表示层分开,就可以最大化从用代码。

4、有利于标准化.

MVC的缺点

1、有时会导致级联的修改。这种修改尤其体现在自上而下的方向。如果在表示层中需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据访问层中都增加相应的代码。

2、降低了系统的性能。这是不言而喻的。如果不采用分层式结构,很多业务可以直接造访数据库,以此获取相应的数据,如今却必须通过中间层来完成。

3、增加理解的复杂度。由于它没有明确的定义,所以完全理解MVC并不是很容易。

常见的MVC框架

常见的服务器端MVC框架有:Struts、Spring MVC、ASP.NET MVC、Zend Framework、JSF;常见前端MVC框架:vue、angularjs、react、backbone;由MVC演化出了另外一些模式如:MVP、MVVM。

什么是Spring MVC

Spring MVC概述

Spring MVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架。Spring MVC的特点:

  1. 轻量
  2. 高效
  3. 与Spring兼容性好
  4. 功能强大。RESTful、数据验证、格式化、绑定机制、本地化、主题等
  5. 简洁灵活

Spring MVC功能

Spring MVC围绕DispatcherServlet设计。 DispatcherServlet的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解的Controller声明方式。官网上说Spring的Web模块提供了大量独特的功能,包括:

  • 清晰的角色划分:控制器(controller)、验证器(validator)、 命令对象(command object)、表单对象(form object)、模型对象(model object)、 Servlet分发器(DispatcherServlet)、 处理器映射(handler mapping)、视图解析器(view resolver)等等。 每一个角色都可以由一个专门的对象来实现。
  • 强大而直接的配置方式:将框架类和应用程序类都能作为JavaBean配置,支持跨多个context的引用,例如,在web控制器中对业务对象和验证器(validator)的引用。
  • 可适配、非侵入:可以根据不同的应用场景,选择合适的控制器子类 (simple型、command型、form型、wizard型、multi-action型或者自定义),而不是从单一控制器 (比如Action/ActionForm)继承。
  • 可重用的业务代码:可以使用现有的业务对象作为命令或表单对象,而不需要去扩展某个特定框架的基类。
  • 可定制的绑定(binding) 和验证(validation):比如将类型不匹配作为应用级的验证错误, 这可以保存错误的值。再比如本地化的日期和数字绑定等等。在其他某些框架中,你只能使用字符串表单对象, 需要手动解析它并转换到业务对象。
  • 可定制的Handler Mapping和View Resolution:Spring提供从最简单的URL映射, 到复杂的、专用的定制策略。与某些web MVC框架强制开发人员使用单一特定技术相比,Spring显得更加灵活。
  • 灵活的Model转换:在Springweb框架中,使用基于Map的 键/值对来达到轻易地与各种视图技术的集成。
  • 可定制的本地化和主题(Theme)解析:支持在JSP中可选择地使用Spring标签库、支持JSTL、支持Velocity(不需要额外的中间层)等等。
  • 简单而强大的JSP标签库(Spring Tag Library):支持包括诸如数据绑定和主题(Theme) 之类的许多功能。它提供在标记方面的最大灵活性。
  • JSP表单标签库:在Spring2.0中引入的表单标签库,使得在JSP中编写 表单更加容易。

Spring MVC 快速入门

示例:

1、可以通过module创建工程,再通过"JBLJavaToWeb"插件把Java项目改为Web项目。

2、直接maven创建一个webapp工程,再添加对应的文件夹和包。

pom.xml

<?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>com.self</groupId>
  <artifactId>hellospringmvc</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <dependencies>
    <!--SpringMVC依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.0.2.RELEASE</version>
    </dependency>
  </dependencies>
</project>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://java.sun.com/xml/ns/javaee"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   version="2.5">
   <!-- 配置核心控制器 :DispatcherServlet -->
   <servlet>
      <servlet-name>hellospringmvc</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <!-- springmvc配置文件加载路径
           1)默认情况下,读取WEB-INF下面的default-servlet.xml文件
           2)可以改为加载类路径下(resources目录),加上classpath:
       -->
      <init-param>
         <param-name>contextConfigLocation</param-name>
         <param-value>WEB-INF/hellospringmvc-servlet.xml</param-value>
      </init-param>
      <!--init-param必须放在load-on-startup前,否则会报错:invalid content was found starting with element 'init-param'. One of '{"http://java.sun.com/xml/ns/javaee":run-as, "http://java.sun.com/xml/ns/javaee":security-role-ref}' is expected-->
      <!--
         DispatcherServlet对象创建时间问题
            1)默认情况下,第一次访问该Servlet时创建对象,默认是访问时创建,意味着在这个时间才去加载hellospringmvc-servlet.xml
            2)可以改变为在项目启动时候就创建该Servlet,提高用户访问体验。
                <load-on-startup>1</load-on-startup>
                      数值越大,对象创建优先级越低! (数值越低,越先创建)
      -->
      <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
      <servlet-name>hellospringmvc</servlet-name>
      <url-pattern>/</url-pattern>
   </servlet-mapping>
</web-app>

注意事项:

  • DispathcerServlet是Spring MVC提供的核心控制器,这是一个Servlet程序,该Servlet会接收所有请求。
  • 核心控制器会读取一个hellospringmvc-servlet.xml配置,加载Spring MVC的核心配置
  • 配置/,代表拦截所以请求。
  • 代表在项目启动时实例化DispathcerServlet,如果没有配置,则在第一次访问Servlet时进行实例化

hellospringmvc-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/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">
    <!-- 1.扫描Controller的包-->
    <context:component-scan base-package="com.self" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <!-- 2.配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 2.1 页面前缀 -->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!-- 2.2 页面后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 3.开启mvc注解驱动-->
    <!--不添加也能使用,高版本spring已经默认实现了。
在Spring中一般采用@RequestMapping注解来完成映射关系,要想使@RequestMapping注解生效必须向上下文中注册DefaultAnnotationHandlerMapping和一个AnnotationMethodHandlerAdapter实例,这两个实例分别在类级别和方法级别处理。而<mvc:annotation-driven/>配置帮助我们自动完成上述两个实例的注入。
    -->
    <!--<mvc:annotation-driven/>-->
</beans>

Controller控制器

控制器类是开发Spring MVC程序过程写的最多的类了。控制器类通常叫Controller,在里面编写接收参数,调用业务方法,返回视图页面等逻辑。

@Controller注解是为了让Spring IOC容器初始化时自动扫描到该Controller类;@RequestMapping是为了映射请求路径,这里因为类与方法上都有映射所以访问时应该是/hello;方法返回的结果是视图的名称success,该名称不是完整页面路径,最终会经过视图解析器解析为完整页面路径并跳转。

@Controller
public class HelloController {
    @RequestMapping("/hello")
    public String sayHello() {
        System.out.println("----------HelloController-----sayHello---------------");
        //return "Hello Spring MVP";
        return "success";
    }
}

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Hello Spring MVP</title>
</head>
<body>
<h1>Hello Spring MVPPPPPPPPPPPPPPPPPPPPPP!</h1>
</body>
</html>

请求:http://localhost:8080/hellospringmvc/hello

输出:

报错:

1、UnsupportedClassVersionError : Unsupported major.minor version 52.0 。

报错如下。

Caused by: java.lang.UnsupportedClassVersionError: org/springframework/web/SpringServletContainerInitializer : Unsupported major.minor version 52.0 (unable to load class org.springframework.web.SpringServletContainerInitializer)

A:这是因为JDK版本过低了,spring5.X要求JDK版本要8及以上,目前配置的是JDK7,版本不符合要求了。换成JDK8即可。

2、请求404,获取/hellospringmvc/WEB-INF/pagessuccess.jsp 没有结果。

报错如下:

A:这是因为在配置springmvc配置文件hellospringmvc-servlet.xml时配置视图解析器没配置好,很明显确实了/斜杠分隔符。这视图解析器视图根据前后缀来拼接jsp视图的路径时由于前缀少了斜杠而导致映射路径不对,加上即可,在新增jsp视图时也要按照格式规范在/WEB-INF/pages/路径下添加jsp页面,否则会导致视图解析器拼接时报错。

    <!-- 2.配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 2.1 页面前缀 -->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!-- 2.2 页面后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

Spring MVC 执行流程分析

Spring MVC执行流程图

  1. 用户发送出请求到前端控制器DispatcherServlet。
  2. DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。
  3. HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
  4. DispatcherServlet调用HandlerAdapter(处理器适配器)。
  5. HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。
  6. Controller执行完成返回ModelAndView对象。
  7. HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。
  8. DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。
  9. ViewReslover解析后返回具体View(视图)。
  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
  11. DispatcherServlet响应用户。

关键组件分析

  1. 前端控制器:DispatcherServlet(不需要程序员开发),由框架提供,在web.xml中配置。
    作用:接收请求,响应结果,相当于转发器,中央处理器。
  2. 处理器映射器:HandlerMapping(不需要程序员开发),由框架提供。
    作用:根据请求的url查找Handler(处理器/Controller),可以通过XML和注解方式来映射。
  3. 处理器适配器:HandlerAdapter(不需要程序员开发),由框架提供。
    作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler。
  4. 处理器:Handler(也称之为Controller,需要工程师开发)。
    注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler。
    作用:接受用户请求信息,调用业务方法处理请求,也称之为后端控制器。
  5. 视图解析器:ViewResolver(不需要程序员开发),由框架提供。
    作用:进行视图解析,把逻辑视图名解析成真正的物理视图。
  6. 视图:View(需要前端工程师开发)。
    作用:把数据展现给用户的页面,View是一个接口,实现类支持不同的View技术(Jsp、Freemarker、Pdf等)

Spring MVC 三大组件

  • 处理器映射器(HandlerMapper)
  • 处理器适配器(HandlerAdapter)
  • 视图解析器(ViewResolver)。

处理映射器

处理映射器作用

通过处理器映射,你可以将Web 请求映射到正确的处理器 Controller 上。当接收到请求时,DispactherServlet 将请求交给 HandlerMapping 处理器映射,让他检查请求并找到一个合适的HandlerExecutionChain,这个HandlerExecutionChain 包含一个能处理该请求的处理器 Controller。然后,DispactherServlet 执行在HandlerExecutionChain 中的处理器 Controller。

Spring内置了许多处理器映射策略,目前主要由三个实现。

SimpleUrlHandlerMapping、
BeanNameUrlHandlerMapping
RequestMappingHandlerMapping。
//注意:Spring MVC3.1之前使用DefaultAnnotationHandlerMapping,Spring MVC3.1之后改为RequestMappingHandlerMapping。

1)SimpleUrlHandlerMapping

SimpleUrlHandlerMapping 在应用上下文中可以进行配置,并且有Ant风格的路径匹配功能。例如我们在springmvc.xml 中配置一个SimpleUrlHandlerMapping 处理器映射。

实例:

simple-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/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">
    <!--1.创建SimpleUrlHandlerMapping-->
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/hello">simpleController</prop>
                <!--如果有更多的请求映射就在这边添加映射配置-->
            </props>
        </property>
    </bean>
    <!--2.创建Controller对象-->
    <bean id="simpleController" class="com.self.controller.SimpleController"/>
    <!--每个请求Controller或者说Handler就要配置一个bean对象-->

    <!-- 3.配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 3.1 页面前缀 -->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!-- 3.2 页面后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

web.xml

<init-param>
   <param-name>contextConfigLocation</param-name>
   <!--<param-value>WEB-INF/hellospringmvc-servlet.xml</param-value>-->
   <param-value>WEB-INF/simple-servlet.xml</param-value>
</init-param>

SimpleController

//实现org.springframework.web.servlet.mvc.Controller接口的handleRequest方法
public class SimpleController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("----------SimpleController-----handleRequest---------------");
        ModelAndView mv = new ModelAndView("success");
        return mv;
    }
}

pom.xml

<!--servlet依赖-->
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.0.1</version>
  <scope>provided</scope>
</dependency>

报错:

1、@Override is not allowed when implementing interface method

A:这是因为idea的java编译设置中设置的是jdk1.5的,跟编译器版本问题有关。编译器1.5只支持@Override注释重写父类方法,不支持实现接口方法。将language level设置高于jdk1.5版本即可 。

参考

2)BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping 将收到的Http请求映射到bean的名字上。

示例:

beanname-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/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">
    <!--1.创建BeanNameUrlHandlerMapping-->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

    <!--2.创建Controller对象,这里的id必须页面访问的路径(以斜杠开头)-->
    <!--如果要继续添加映射只要在这里配置好映射路径和注册bean即可-->
    <bean id="/hello" class="com.self.BeanNameController"/>

    <!-- 3.配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 3.1 页面前缀 -->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!-- 3.2 页面后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

//实现org.springframework.web.servlet.mvc.Controller接口的handleRequest方法
public class BeanNameController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("----------BeanNameController-----handleRequest---------------");
        ModelAndView mv = new ModelAndView("success");
        return mv;
    }
}

pom.xml

<!--servlet依赖-->
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.0.1</version>
  <scope>provided</scope>
</dependency>

web.xml

<init-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>WEB-INF/beanname-servlet.xml</param-value>
   <!--<param-value>WEB-INF/hellospringmvc-servlet.xml</param-value>-->
</init-param>

请求:http://localhost:8080/hellospringmvc/hello

注意:在bean的id中要加上斜杆,Controller的代码跟前面的SimpleUrlHandlerMapping一样,实现Controller,重写handlerRequest()方法即可。

在默认情况下,如果没有在上下文中没有找到处理器映射,DispactherServlet 会为你创建一个BeanNameUrlHandlerMapping。

3)RequestMappingHandlerMapping

RequestMappingHandlerMapping是三个中最常用的HandlerMapping,因为注解方式比较通俗易懂,代码界面清晰,只需要在代码前加上@RequestMapping()的相关注释就可以了 。

实例:

@Controller
public class HelloController {
    //@RequestMapping("/hello")
    public String sayHello() {
        System.out.println("----------HelloController-----sayHello---------------");
        //return "Hello Spring MVP";
        return "success";
    }
}

hellospringmvc-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/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">
    <!-- 1.扫描Controller的包-->
    <context:component-scan base-package="com.self" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <!-- 2.配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 2.1 页面前缀 -->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!-- 2.2 页面后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 3.开启mvc注解驱动-->
    <!--不添加也能使用,高版本spring已经默认实现了。
在Spring中一般采用@RequestMapping注解来完成映射关系,要想使@RequestMapping注解生效必须向上下文中注册DefaultAnnotationHandlerMapping和一个AnnotationMethodHandlerAdapter实例,这两个实例分别在类级别和方法级别处理。而<mvc:annotation-driven/>配置帮助我们自动完成上述两个实例的注入。
    -->
    <!--<mvc:annotation-driven/>-->
</beans>

注意:重点是添加mvc:annotation-driven/标签! 这个要在有其他HandlerMapping接口的情况下才需要。

处理适配器

处理器适配器作用

HandlerAdapter字面上的意思就是处理适配器,它的作用就是调用具体的方法对用户发来的请求来进行处理。当HandlerMapping获取到执行请求的Controller时,DispatcherServlte会根据Controller对应的Controller类型来调用相应的HandlerAdapter来进行处理。

HandlerAdapter的实现有:

  • HttpRequestHandlerAdapter
  • SimpleServletHandlerAdapter
  • SimpleControllerHandlerAdapter
  • AnnotationMethodHandlerAdapter ——(Spring MVC 3.1后已废弃)
  • RequestMappingHandlerAdapter。

1)HttpRequestHandlerAdapter

HttpRequestHandlerAdapter可以处理类型为HttpRequestHandler的handler,对handler的处理是调用HttpRequestHandler的handleRequest()方法。

示例:

public class HttpRequestHandlerController implements HttpRequestHandler{

    @Override
    public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
        //HttpRequestHandlerAdapter用来处理HttpRequestHandler类型的Controller
        //注意该类型Controller都没有ModelAndView,相当于servlet
        httpServletResponse.getWriter().write("Hello HttpRequestHandler.");
    }
}

httprequesthandler-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/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">
    <!--1.创建BeanNameUrlHandlerMapping-->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

    <!--2.创建HttpRequestHandlerAdapter,用来处理HttpRequestHandler类型的Controller-->
    <bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"/>

    <!--3.创建Controller对象,这里的id必须页面访问的路径(以斜杠开头)-->
    <!--如果要继续添加映射只要在这里配置好映射路径和注册bean即可-->
    <bean id="/hi" class="com.self.HttpRequestHandlerController"/>

</beans>

输出:

报错:The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler 。

A:这是因为创建HttpRequestHandlerAdapter,用来处理HttpRequestHandler类型的Controller,而public class BeanNameController implements Controller 不是HttpRequestHandler类型的Controller,处理不了,所以报错,应该换上合适的HandlerAdapter,如RequestMappingHandlerAdapter。

2)SimpleServletHandlerAdapter

SimpleServletHandlerAdapter可以处理类型为Servlet,就是把Servlet当做Controller来处理,使用Servlet的service方法处理用户请求。 业务Controller继承HttpServlet 实现doGet()或doPost()方法。

示例:

public class SimpleServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //SimpleServletHandlerAdapter用来处理HttpServlet类型的Controller
        resp.getWriter().write("Hello HttpServlet.");
    }

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

httpservletHandler-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/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">
    <!--1.创建BeanNameUrlHandlerMapping-->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

    <!--2.创建SimpleServletHandlerAdapter-->
    <bean class="org.springframework.web.servlet.handler.SimpleServletHandlerAdapter"/>

    <!--3.创建Controller对象,这里的id必须页面访问的路径(以斜杠开头)-->
    <!--如果要继续添加映射只要在这里配置好映射路径和注册bean即可-->
    <bean id="/hi" class="com.self.SimpleServlet"/>

</beans>

输出:

3)SimpleControllerHandlerAdapter

SimpleControllerHandlerAdapter可以处理类为Controller的控制器,使用Controller的handlerRequest方法处理用户请求。

beanname-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/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">
    <!--1.创建BeanNameUrlHandlerMapping-->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

    <!--2.创建Controller对象,这里的id必须页面访问的路径(以斜杠开头)-->
    <!--如果要继续添加映射只要在这里配置好映射路径和注册bean即可-->
    <bean id="/hi" class="com.self.BeanNameController"/>

    <!-- 3.配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 3.1 页面前缀 -->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!-- 3.2 页面后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

    <!--默认不配置SimpleControllerHandlerAdapter, 也能处理Controller类型为org.springframework.web.servlet.mvc.Controller的接口,
    SimpleControllerHandlerAdapter会默认注册-->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
</beans>

//实现org.springframework.web.servlet.mvc.Controller接口的handleRequest方法
public class BeanNameController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("----------BeanNameController-----handleRequest---------------");
        ModelAndView mv = new ModelAndView("success");
        return mv;
    }
}

4)RequestMappingHandlerAdapter

RequestMappingHandlerAdapter可以处理类型为HandlerMethod的控制器,通过Java反射调用HandlerMethod的方法来处理用户请求。

补充:类型为HandlerMethod的控制器是在容器初始化时,通过RequestMappingHandlerMapping在添加映射时把我们写业务Controller转化为HandlerMethod类型,存储在LinkedHashMap中。

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#createHandlerMethod

注意:

如果有在web.xml中配置指定的HandlerMapping 和 HandlerAdapter 的话,则只注册配置的处理器。

示例:

hellospringmvc-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/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">
    <!-- 1.扫描Controller的包-->
    <context:component-scan base-package="com.self" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--2.创建RequestMappingHandlerMapping-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

    <!--3.创建RequestMappingHandlerAdapter-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

    <!-- 4.配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 4.1 页面前缀 -->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!-- 4.2 页面后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

@Controller
public class HelloController {
    @RequestMapping("/hello")
    public String sayHello() {
        System.out.println("----------HelloController-----sayHello---------------");
        //return "Hello Spring MVP";
        return "success";
    }
}

视图解析器

视图解析器作用

Spring MVC中的视图解析器的主要作用就是将逻辑视图转换成用户可以看到的物理视图。

流转逻辑:

当用户对SpringMVC应用程序发起请求时,这些请求都会被Spring MVC的DispatcherServlet处理,通过处理器找到最为合适的HandlerMapping定义的请求映射中最为合适的映射,然后通过HandlerMapping找到相对应的Handler,然后再通过相对应的HandlerAdapter处理该Handler。返回结果是一个ModelAndView对象,当该ModelAndView对象中不包含真正的视图,而是一个逻辑视图路径的时候,ViewResolver就会把该逻辑视图路径解析为真正的View视图对象,然后通过View的渲染,将最终结果返回给用户。

SpringMVC中处理视图最终要的两个接口就是ViewResolver和View,ViewResolver的作用是将逻辑视图解析成物理视图,View的主要作用是调用其render()方法将物理视图进行渲染。

常见的视图解析器

以下为Spring MVC提供常见视图解析器:

视图类型 说明
BeanNameViewResolver 将逻辑视图名称解析为一个Bean,Bean的Id等于逻辑视图名
InternalResourceViewResolver将视图名解析为一个URL文件,一般使用该解析器将视图名映射为一个保存在WEB-INF目录下的程序文件,如 JSP
JaperReportsViewResolver JapserReports是基于Java的开源报表工具,该解析器解析为报表文件对应的URL
FreeMarkerViewResolver 解析为基于FreeMarker模板的模板文件
VelocityViewResolver 解析为Velocity模板技术的模板文件
VelocityLayoutViewResolver 解析为Velocity模板技术的模板文件

其中,最常用的是InternalResourceViewResolver,配置如下:

<!-- 4.配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!-- 4.1 页面前缀 -->
    <property name="prefix" value="/WEB-INF/pages/"/>
    <!-- 4.2 页面后缀 -->
    <property name="suffix" value=".jsp"/>
</bean>

Spring MVC 核心源码分析

DispathcerServlet属性配置

Spring MVC核心包里面有一个配置文件:DispathcerServlet.properties

E:/localwarehouse/org/springframework/spring-webmvc/5.0.2.RELEASE/spring-webmvc-5.0.2.RELEASE.jar!/org/springframework/web/servlet/DispatcherServlet.properties

该配置提供许多的默认组件,这些组件为Spring MVC的执行提供了支持,其中划线部分就是我们之前说到的Spring MVC三大组件。

doDispatch方法源码追踪

DispatcherServlet类的层次结构 :

从Spring MVC的执行流程我们知道,用户的请求最先到达就是DispatcherServlet,它是Spring MVC的核心,可以把它叫做中央处理器,因此我们分析源码之前,先看看他是什么样的流程,通过源码可以看出,它是继承FrameworkServlet,它也是Spring MVC提供的类,继续往下继承关系看,FrameworkServlet继承HttpServletBean,它是Spring提供的类,最终直到到它继承HttpServlet。

DispathcerServlet既然是Servlet,那么它肯定有一个service方法(Servlet最核心的方法),我们看这个方法在哪里实现的,一个个看,发现HttpServletBean并没有,在FrameworkServlet中,因此Spring实现这个serivice方法在这里实现的。

serivice()方法的作用的就是得到客户端的请求,然后判断这个请求是不是PATCH请求方式,如果不是就调用父类(HttpServlet)中的service方法,我们调用父类这个service方法其实实际是调用该类的doGet方法或者doPost方法等等,拿到不同的请求方式处理不同的业务,我们以get方式为例:

进入到processRequest方法,直接进入doService方法.跳到DispatcherServlet这个类里面来了,其实doSerivice可以猜到被子类各种重写 。

到此为止,我们知道DispatcherServlet作为Spring MVC的核心控制器,把用户请求最终转到了它里面的doDispatch()方法来完成具体工作。

处理器映射器核心源码

在doDispathcer方法里面,首先是创建一个ModelAndView对象 = null,判断当前请求是不是二进制(文件上传)的请求。

processedRequest = this.checkMultipart(request)

再往下看代码:

mappedHandler = this.getHandler(processedRequest);

这句代码非常重要!这是根据当前请求去拿一个Handler(控制器),这个Handler其实就是我们写的Controller类,进入到getHandler方法的源码:

有个handlerMappings的List集合,该集合存储了当前环境所有HandlerMapping对象,通过遍历该List集合,通过最合适的HandlerMapping找到Handler对象。

找到了Handler对象后,返回的是一个HandlerExecutionChain类型的Handle,这里面封装了一个HelloController,也就是我们自己的创建Controlller,如果有配置拦截器,还会使用一个interceptors集合封装所有拦截器。

处理器适配器核心源码

doDispatcher方法我们往下看,会到以下代码:

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

Spring MVC会根据当前环境的配置和代码去选择一个合适的HandlerAdapter实现类来执行Handler。

最后,handler方法会返回ModelAndView对象。该对象交给给后面的视图解析器解析处理。

视图解析器核心源码

回到DispathcerServlet的doDispatcher方法,我们往下走,看到这行代码:

this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);

这行代码包含了Spring MVC获取对应的View以及选择合适的ViewResolver解析视图的逻辑。

@RequestMapping注解

@RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。

@RequestMapping常用属性

value属性

指定控制器的方法URI 。

如果类和方法上都指定value值,那么方法的最终方法路径为:http://localhost:8080/hellospringmvc/say/hello

@Controller
@RequestMapping("/say")
public class HelloController {
    @RequestMapping("/hello")
    public String sayHello() {
        System.out.println("----------HelloController-----sayHello---------------");
        //return "Hello Spring MVP";
        return "success";
    }
}

method属性

指定请求的method类型,可以接受GET,POST,PUT,DELETE等.

@Controller
@RequestMapping("/say")
public class HelloController {
    @RequestMapping(value = "/hello",method = RequestMethod.GET)
    public String sayHello() {
        System.out.println("----------HelloController-----sayHello---------------");
        //return "Hello Spring MVP";
        return "success";
    }
}

注意:当注解有两个值时,就要有键值对对应关系,不能使用默认的值。

   @RequestMapping("/hello")
   //注解有两个值时使用下面的写法
    @RequestMapping(value = "/hello",method = RequestMethod.GET)

consumes、produces属性

consumes:指定处理请求的提交内容类型(Content-Type),例如application/json, text/html; produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回。

@RequestMapping(value = "/hello.do",consumes = "application/json",produces = "application/json")
public void hello(HttpServletRequest request,HttpServletResponse response) throws IOException {
    response.getWriter().write("Hello-World");
}

报错:

使用时会报415.The server refused this request because the request entity is in a format not supported by the requested resource for the requested method.

待解决。

params、headers属性

params:指定request中必须包含某些参数值,才让该方法处理。

headers:指定request中必须包含某些指定的header值,才能让该方法处理请求。

params示例:

@RequestMapping(value = "/hello",method = RequestMethod.GET,params = "id=3")
public String sayHello() {
    System.out.println("----------HelloController-----sayHello---------------");
    //return "Hello Spring MVP";
    return "success";
}

输出:

headers示例:

@RequestMapping(value = "/hello.do",headers = "Referer=http://www.hello.com/")
public void hello(HttpServletRequest request,HttpServletResponse response) throws IOException {
   response.getWriter().write("Hello");
}

Spring MVC支持对多种类型的请求参数进行封装

  • 基本类型
  • Pojo对象类型
  • 包装Pojo对象类型
  • List集合类型
  • Map集合类型

Spring MVC 基本类型封装

示例:

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Spring MVC</title>
</head>
<body>
<h2>基本类型参数封装</h2>
<form action="http://localhost:8080/hellospringmvc/say/helloParam">
    用户名:<input type="text" name="name"><br>
    年龄:<input type="text" name="age"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

@RequestMapping("/helloParam")
public String helloParam(String name, Integer age) {
    System.out.println("用户名:" + name);
    System.out.println("年龄:" + age);
    return "success";
}

输出:

用户名:恶龙吟风
年龄:null
------------------------------
用户名:恶龙吟风
年龄:365
-------------------------------
用户名:
年龄:365

注意:

当接收的基本类型参数不是包装类时,如 int age,这时候要求age必须有值传进来,否则报错HTTP Status 400 -错误。The request sent by the client was syntactically incorrect.
如果是用包装类接收,如Integer age,则没有传值时age = null,String name 没有值默认空字符串 name=""

Spring MVC Post中文乱码

在Spring MVC表单如果是Post方法提交中文内容时,会出现乱码 。

这是我们可以配置Spring MVC提供字符编码过滤器来解决问题。

配置字符编码过滤器

web.xml

    <!--字符编码过滤器-->
   <!-- 字符编码过滤器的配置效果是当我们没有指定请求响应的编码格式时,该字符编码过滤器会
    默认以配置的编码格式去解码和编码,如配置的是utf-8-->
    <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>
 <!--       下面配置是否强制设置编码格式为指定的转换编码格式:utf-8
        如果强制,则所以的请求响应都会按照utf-8来解码和编码,当请求的格式是GBK等其他编码时,
        就会因此而乱码,所以一般是不配置这个选项的。
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>-->
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

@RequestParam注解

在SpringMvc后台进行获取数据,一般有三种:

  1. request.getParameter(“参数名”)
  2. 用@RequestParam注解获取
  3. Springmvc默认支持的数据类型接收参数,可直接通过controller方法参数对应jsp中请求参数name直接获取

其实可以不使用@RequestParam注解,直接接收,如果不使用该注解要求controller方法中的参数名称要跟form中name名称一致,使用该注解只是方便随意取参数名称,不过value属性还是要与name一致,该注解只适合一些任性的盆友使用 。

使用

@RequestParam作用:将请求参数绑定到你控制器的方法参数上(是springmvc中接收普通参数的注解) 。

在使用SpringMVC接收基本参数类型的时候,我们发现如果控制器中形参的名称与表单的name名称不一致时,无法接收参数。这是可以使用@RequestParam注解解决这个问题。

注意:页面表单的name和控制器的形参(方法参数)并不一致,但是@RequestParam注解的value值(即接收请求参数的名称)必须和页面表单的name保持一致。否则就接收不到参数。

@RequestParam里name和value的区别:没有区别。源码注解的意思就是name的别名是value,value的别名是name。二者皆可,并且开发中两个都能获得参数,获得一样的结果。

@RequestParam注解还有两个属性:

  1. required:参数是否必须。默认为true。代表页面是否必须传递该参数。如果该值为true,但没有传递参数,会报错。
  2. defaultValue:默认值。代表如果页面没有传递该参数,使用defaultValue的值代替。当有设置defaultValue时,required属性失效,默认是false。如果没有传该参数,就使用默认值 。

实例:

 @RequestMapping(value ="/requestParam",method = RequestMethod.GET)
    public String requestParam(@RequestParam("userName") String name, @RequestParam("userAge")Integer userAge) {
        System.out.println("用户名:" + name);
        System.out.println("年龄:" + userAge);
        return "success";
    }

    /**
     *
     * @param name 默认defaultValue= "大青山",required = true失效,为false
     * @param userAge value = "userAge" ,required = false 可以不传,不传时为null
     * @return
     */
    @RequestMapping(value ="/requestParam1",method = RequestMethod.GET)
    public String requestParam1(@RequestParam(value = "userName" ,required = true,defaultValue = "大青山") String name, @RequestParam(value = "userAge" ,required = false)Integer userAge) {
        System.out.println("用户名:" + name);
        System.out.println("年龄:" + userAge);
        return "success";
    }

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Spring MVC</title>
</head>
<body>
<h2>基本类型参数封装</h2>
<form action="http://localhost:8080/hellospringmvc/say/requestParam1">
    用户名:<input type="text" name="userName"><br>
    年龄:<input type="text" name="userAge"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

Spring MVC Pojo参数封装

之前我们接收参数的时候都是定义一个个的基本类型来接收,这样比较繁琐,Spring MVC提供了使用Pojo(或者称为JavaBean)类型来封装请求参数。

实例:

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Spring MVC</title>
</head>
<body>
<h2>基本类型参数封装</h2>
<%--设置请求类型为post--%>
<form action="http://localhost:8080/hellospringmvc/say/helloParamPojo" method="post">
    用户名:<input type="text" name="userName"><br>
    年龄:<input type="text" name="userAge"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

/**
 * method = RequestMethod.POST 可以不标记,不标记就都能匹配
 * @param user
 * @return
 */
@RequestMapping(value ="/helloParamPojo" ,method = RequestMethod.POST)
public String helloParamPojo(User user) {
    System.out.println("用户名:" + user.getUserName());
    System.out.println("年龄:" + user.getUserAge());
    return "success";
}

public class User {

    private String userName;

    private Integer userAge;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Integer getUserAge() {
        return userAge;
    }

    public void setUserAge(Integer userAge) {
        this.userAge = userAge;
    }
}

Spring MVC 包装参数封装——Pojo嵌套Pojo对象

在Spring MVC的应用过程中,我们在后端经过需要将表单数据封装在一个包装Pojo类型中,所谓包装Pojo类型,就是Pojo对象中包含另一个Pojo对象。

示例:

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Spring MVC</title>
</head>
<body>
<h2>基本类型参数封装</h2>
<%--设置请求类型为post--%>
<form action="http://localhost:8080/hellospringmvc/say/helloParamPojos" method="post">
    用户名:<input type="text" name="userName"><br>
    年龄:<input type="text" name="userAge"><br>
    <%--封装用户的地址信息,name为address.province这种写法,这代表把数据封装到User对象->Address对象的province属性中。--%>
    省份:<input type="text" name="address.province"><br>
    城市:<input type="text" name="address.city"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

public class User {

    private String userName;

    private Integer userAge;

    private Address address;
    //......
    }

public class Address {

    private String province;

    private String city;
    //......
    }

@RequestMapping(value ="/helloParamPojos" ,method = RequestMethod.POST)
public String helloParamPojos(User user) {
    System.out.println("用户名:" + user.getUserName());
    System.out.println("年龄:" + user.getUserAge());
    System.out.println("省份:"+user.getAddress().getProvince());
    System.out.println("城市:"+user.getAddress().getCity());
    return "success";
}

输出:

用户名:艾米
年龄:18
省份:艾米帝国
城市:帝都

Spring MVC List集合参数封装

我们是一个Address对象来接收一个地址信息,如果有多个地址信息怎么呢?这时我们可以使用List集合来封装。

示例:

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Spring MVC</title>
</head>
<body>
<h2>基本类型参数封装</h2>
<%--设置请求类型为post--%>
<form action="http://localhost:8080/hellospringmvc/say/helloParamList" method="post">
    用户名:<input type="text" name="userName"><br>
    年龄:<input type="text" name="userAge"><br>
    <%--address[0].province,代表给User对象->List<Address>集合->第一个Address对象的province属性赋值--%>
    省份1:<input type="text" name="address[0].province"><br>
    城市1:<input type="text" name="address[0].city"><br>
    省份2:<input type="text" name="address[1].province"><br>
    城市2:<input type="text" name="address[1].city"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

public class User {

    private String userName;

    private Integer userAge;

    private List<Address> address;
     //......
    }

@RequestMapping(value ="/helloParamList" ,method = RequestMethod.POST)
public String helloParamList(User user) {
    System.out.println("用户名:" + user.getUserName());
    System.out.println("年龄:" + user.getUserAge());
    List<Address> address = user.getAddress();
    for (Address addressTemp : address) {
        System.out.println("省份:"+addressTemp.getProvince());
        System.out.println("城市:"+addressTemp.getCity());
    }
    return "success";
}

用户名:许三多
年龄:25
省份:上海
城市:魔都
省份:北京
城市:帝都

Spring MVC Map集合参数封装

我们利用List集合来封装多个地址信息,其实把List集合换成Map集合也是可以的 。

示例:

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Spring MVC</title>
</head>
<body>
<h2>基本类型参数封装</h2>
<%--设置请求类型为post--%>
<form action="http://localhost:8080/hellospringmvc/say/helloParamMap" method="post">
    用户名:<input type="text" name="userName"><br>
    年龄:<input type="text" name="userAge"><br>
    <%--这里的address['a1'].city,a1是赋值给Map的key,city是赋值给Address的city属性--%>
    省份1:<input type="text" name="address['a1'].province"><br>
    城市1:<input type="text" name="address['a1'].city"><br>
    省份2:<input type="text" name="address['a2'].province"><br>
    城市2:<input type="text" name="address['a2'].city"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

注意:这里的address['a1'].city,a1是赋值给Map的key,city是赋值给Address的city属性 .

@RequestMapping(value ="/helloParamMap" ,method = RequestMethod.POST)
public String helloParamMap(User user) {
    System.out.println("用户名:" + user.getUserName());
    System.out.println("年龄:" + user.getUserAge());
    Map<String, Address> addressMap = user.getAddress();
    Set<Map.Entry<String, Address>> entries = addressMap.entrySet();
    for (Map.Entry<String, Address> entry : entries) {
        System.out.println(entry.getKey()+"--"+JSON.toJSONString(entry.getValue()));
    }
    return "success";
}

public class User {

    private String userName;

    private Integer userAge;

    private Map<String,Address> address;
       //......
    }

用户名:艾米
年龄:18
a1--{"city":"深圳","province":"粤"}
a2--{"city":"金门","province":"闽"}

Spring MVC 自定义类型转换

Spring MVC默认情况下可以对基本类型进行类型转换,例如可以将String转换为Integer,Double,Float等。但是Spring MVC并不能转换日期类型(java.util.Date),如果希望把字符串参数转换为日期类型,必须自定义类型转换器。接下来讲解如何自定义类型转换器。

//一般项目不会用上

Spring MVC 使用Servlet API

在Spring MVC应用中,我们也经常需要使用到原生的Servlet API来满足功能的开发需求。接下来介绍如何在Spring MVC中使用Servlet 相关API。

实例:

@RequestMapping("/leap")
public void leap(HttpServletRequest request,
                   HttpServletResponse response,
                   HttpSession session)
        throws IOException {
    request.setAttribute("request","take leap of faith");
    session.setAttribute("session","take leap of faith");
    response.sendRedirect("/hellospringmvc/say/hello");
}

@RequestHeader注解

Spring MVC提供@RequestHeader注解方便我们获取请求头信息。

示例:

@RequestMapping(value ="/helloHeader")
public String helloHeader(@RequestHeader("host") String host, @RequestHeader("accept") String accept) {
    System.out.println("host---"+host);
    System.out.println("accept---"+accept);
    return "success";
}

输出:

host---localhost:8080
accept---text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

@CookieValue注解

Spring MVC提供@CookieValue方便我们获取指定Cookie数据。

示例:

@RequestMapping(value ="/helloCookie")
public String helloHeader(@CookieValue("JSESSIONID") String sessionId) {
    System.out.println("JSESSIONID---"+sessionId);
    return "success";
}

Spring MVC 静态资源访问

无法访问静态资源的原因

当Spring MVC配置的拦截路径为 / 或 /* 的时候,我们项目会无法访问静态资源文件,如:

springmvc.xml配置:

<servlet-mapping>
	<servlet-name>dispatcherServlet</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>

三种Spring MVC访问静态资源的方案

1、通过Tomcat的DefaultServlet配置访问静态资源。

在我们应用的web.xml重新配置DefaultServlet的映射路径,让其对特定的静态资源进行处理。

首先,我们要明白在Spring MVC应用之所以访问不了静态资源,是因为我们配置的DispathcerServlet映射路径覆盖了Tomcat的DefaultServlet的映射路径。

Tomcat的DefaultServlet配置,在Tomcat根目录的conf/web.xml

实现方式,在项目的web.xml配置:

<!--重新配置Tomcat的DefaultServlet的映射路径-->
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.html</url-pattern>
    <url-pattern>*.jpg</url-pattern>
    <url-pattern>*.css</url-pattern>
    <url-pattern>*.js</url-pattern>
    <url-pattern>*.png</url-pattern>
    <url-pattern>*.gif</url-pattern>
</servlet-mapping>

2、通过mvc:resources/标签,把页面的不同请求,转发到项目内部的某个目录下

Spring MVC提供了mvc:resources/标签,该标签的作用可以把页面的不同请求,转发到项目内部的某个目录下。该标签配置在hellospringmvc-servlet.xml文件下。


<!--静态资源处理-->
<mvc:resources mapping="/images/**" location="/images/"/>
<mvc:resources mapping="/css/**" location="/css/"/>
<mvc:resources mapping="/js/**" location="/js/"/>

mapping:代表映射页面的访问路径。

location:代表项目内的具体的物理路径地址。

3、通过mvc:default-servlet-handler/把所有静态资源目录的文件对外映射出去。

第二种方案的使用比较繁琐,因为需要一个个目录进行配置,其实有一个更加方便的标签:

mvc:default-servlet-handler/,该标签相当于一次帮助我们把所有静态资源目录的文件对外映射出去。该标签配置在hellospringmvc-servlet.xml文件下。


  <!--静态资源处理-->
<mvc:default-servlet-handler/>

Model与ModelMap

Spring MVC应用中,我们经常需要在Controller将数据传递到JSP页面,除了可以通过HttpServletRequest域传递外,Spring MVC还提供了两个Api,分别为Model接口和ModelMap类。

Model与ModelMap的关系

Model接口和ModelMap类都有一个共同的子类:BindingAwareModelMap 。而BindingAwareModelMap底层其实是往HttpServletRequest域存入数据,所以Model接口或者ModelMap的底层也是往request域存入数据!

示例:

show.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>show page</title>
</head>
<body>
<%--可以直接取也可以通过requestScope取值--%>
<%--获取Model数据-${requestScope.question}--%>
获取Model数据-${question}
<hr/>
获取ModelMap数据-${requestScope.answer}
</body>
</html>

@Controller
public class ModelController {

    @RequestMapping("/helloModel")
    public String helloModel(Model model){
        model.addAttribute("question","what do you want?");
        return "show";
    }

    @RequestMapping("/helloModelMap")
    public String helloModelMap(ModelMap modelMap){
        modelMap.addAttribute("answer","I want my phone call!");
        return "show";
    }
}

输出:

@ModelAttribute注解

@ModelAttribute作用

@ModelAttribute注解的作用,将请求参数绑定到Model对象。被@ModelAttribute注释的方法会在Controller每个方法执行前被执行(如果在一个Controller映射到多个URL时,要谨慎使用)。

@ModelAttribute注解的使用总结

@ModelAttribute使用位置

在SpringMVC的Controller中使用@ModelAttribute时,其位置包括下面三种:

  1. 应用在方法上
  2. 应用在方法的参数上
  3. 应用在方法上,并且方法同时使用@RequestMapping

1、应用在方法上

用在无返回值的方法

在model方法之前会执行setAttribute()方法。因此在setAttribute()方法中,请求传递的name参数存入Model对象,该参数值也会随着model方法带到JSP页面中。

实例:

result.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>@ModelAttribute注解的使用</title>
</head>
<body>
${name}
</body>
</html>

@Controller
public class ModelAttributeController {

    @ModelAttribute
    public void setAttribute(@RequestParam(value = "userName",required = false) String name, Model model){
        model.addAttribute("name",name);
    }

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

请求:<http://localhost:8080/hellospringmvc/result?userName=艾米

输出:

用在带返回值的方法

带有返回值的情况,其实就是自动把方法返回值存入Model对象,@ModelAttribute的value属性就是Model的key。相当于

model.addAttribute("name",name);

示例:

@Controller
public class ModelAttributeController {

    @ModelAttribute("name")
    public String setAttribute(@RequestParam(value = "userName",required = false) String name){
        return name;
    }

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

2、应用在方法的参数上

@ModelAttribute注解应用在方法的参数上,其实就是从Model对象中取出对应的属性值。

示例:

@Controller
public class ModelAttributeController {
    @ModelAttribute("name")
    public String setAttribute(@RequestParam(value = "userName",required = false) String name){
        return name;
    }

    @RequestMapping("/resultPara")
    public String resultPara(@ModelAttribute("name") String name){
        System.out.println("name="+name);
        return "result";
    }
}

方法上+@RequestMapping

@ModelAttribute和@RequestMapping同时应用在方法上的时候,有两层意义:

  1. 方法的返回值会存入Model对象中,key就是ModelAttribute的value属性值
  2. 方法的返回值不再是方法的访问路径,访问路径会变为@RequestMapping的value值,例如:@RequestMapping(value = "/model") 跳转的页面是model.jsp页面。

示例:这种使用方式请求的时候是通过其他方法调用进来的。

@RequestMapping("/result")
@ModelAttribute("name")
public String resultMapping(@RequestParam(value = "userName",required = false) String name){
    System.out.println("name="+name);
    return name;
}

@SessionAttributes注解

一般不用这个注解,范围太广,有其他代替方法,最简单的就是用个redis缓存。

@SessionAttributes作用

默认情况下Spring MVC将模型中的数据存储到request域中。当一个请求结束后,数据就失效了。如果要跨页面使用。那么需要使用到session。而@SessionAttributes注解就可以使得模型中的数据存储一份到session域中。

注意:@SessionAttributes注解只能用在类上!

@SessionAttributes属性:

  • name:代表取Model对象的对应属性,存入session域中
  • type:属性类型

示例:

@Controller
@SessionAttributes(names = "name",types = String.class)
public class SessionAttributeController {

    @RequestMapping("/showSession")
    public String showSession(Model model,String name){
        model.addAttribute("name",name);
        return "sessionResult";
    }
}

sessionResult.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>@SessionAttributes注解的使用</title>
</head>
<body>
request:${requestScope.name}<br/>
session:${sessionScope.name}<br/>
</body>
</html>

输出:

Spring MVC 控制器返回值

Spring MVC的控制器方法返回值可以支持多种写法,每种写法的场景和效果都不一样。下面分别来看看每种返回值的使用。

  1. 普通字符串
  2. 转发字符串
  3. 重定字符串
  4. void
  5. ModelAndView
  6. Java对象

普通字符串——jsp页面地址+页面名称

返回普通字符串这种情况比较常见,主要用在我们处理完业务逻辑后,需要跳转到应用的其他页面。

只能转发到视图解析器指定的特定目录。

/**
 * 1)字符串 - 普通字符串(代表页面名称,不是完整路径,最后经过视图解析器的解析)
 *    优势:写法简单
 *    劣势:只能转发到视图解析器指定的特定目录
 */
@RequestMapping("/hello")
public String sayHello() {
    return "success";
}

转发字符串

普通字符串,只能转发到视图解析器指定前缀的目录下的页面,如果想转发到视图解析器目录以外的页面,这时可以使用转发字符串的写法。

forward:完整页面的路径 。

示例:可以传递request域对象数据

/**
 * 2)字符串 - 转发字符串
 *     转发字符串格式:
 *        forward:完整页面的路径      例如:forward:/pages/index.jsp
 *
 *    优势:更加灵活,可以转到本项目下的任何页面,可以传递request域对象数据
 *    劣势:写法稍复杂
 */
@RequestMapping("/forward")
public String forward(){
    return "forward:/index.jsp";
}

重定向字符串

如果希望使用重定向的方式跳转页面,这时可以使用重定向字符串完成。

示例:不能转发reques域对象数据

/**
 * 3)字符串 - 重定向字符串
 *     重定向字符串格式:
 *        redirect:完整页面的路径      例如:redirect:/pages/index.jsp
 *
 *    优势:很灵活,可以重定向到项目内和项目以外的页面
 *    劣势:写法稍复杂,不能转发reques域对象数据
 */
@RequestMapping("/redirect")
public String redirect(){
    return "redirect:http://www.baidu.com";
}

返回空

一般我们在文件下载的时候,就不需要控制器方法返回任何内容,所以设置为void即可。

示例:

/**
 * 4)返回void
 * 用于文件下载
 */
@RequestMapping("/download")
public void download(HttpServletResponse response) {
    //模拟文件下载
    //1.读取需要下载的文件
    File file = new File("e:/car.jpg");

    //2.构建文件输入流
    try (InputStream in = new FileInputStream(file); OutputStream out = response.getOutputStream()) {
        //4.边读边写
        byte[] buf = new byte[1024];
        int len = 0;

        while ((len = in.read(buf)) != -1) {
            out.write(buf, 0, len);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return;
}

ModelAndView

Spring MVC提供了ModelAndView对象,该对象既可以存储数据到request域,也可以设置视图。其实Spring MVC任何处理器适配器最终执行完控制器后,都会返回ModelAndView对象。所以这是一个比较底层的对象。

示例:

 @RequestMapping("/mv")
    public ModelAndView mv(){
        ModelAndView mv = new ModelAndView();
        //设置模型数据 值attributeValue可以是任何对象Object
        mv.addObject("name","池寒枫");
        //设置视图数据
        //设置viewName代表页面名称,不是完整路径,最后经过视图解析器的解析
        mv.setViewName("result");
        return mv;
    }

输出:

返回Java对象

这里返回的Java对象,可能是普通JavaBean,也可以是List或Map集合等。一般希望把控制器的返回Java对象转换为Json字符串,才需要返回Java对象。

Spring MVC JSON数据转换

场景:

在开发中后端经常需要接受来自于前端传递的Json字符串数据并把把Json字符串转换为Java对象 。

后端也经常需要给前端把Java对象数据转换为Json字符串返回 。

办法:

使用@RequestBody和@ResponseBody注解。

Spring MVC默认是无法实现Json数据转换功能的,需要额外导入fastjson包来支持Json数据转换。

编写JsonController,这里用到两个关键注解@RequestBody和@ResponseBody。

  • @RequestBody:完成页面Json字符串转换为后端Java对象
  • @ResponseBody:完成后端Java对象转换为前端Json字符串

示例:

<!-- 平时处理json用的 -->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.29</version>
</dependency>
<!-- jackson支持包 -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.9.5</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>2.9.5</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.5</version>
</dependency>

@Controller
public class JsonController {

    @RequestMapping("/json")
    @ResponseBody
    public MercenaryUser showJson(@RequestBody MercenaryUser user){
        System.out.println("前端发送的数据:"+ JSON.toJSONString(user));
        //后台返回json字符串给前端
        user.setId(1);
        user.setName("艾米");
        user.setAge(20);
        user.setRide("冥牙");
        return user;
    }

    @RequestMapping("/toJson")
    public String toJson(){
        return "json";
    }
}

json.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>JSON格式转换</title>
    <%--使用jQuery实现ajax异步请求后端Controller,同时发送Json字符串对象--%>
    <%--<script src="/hellospringmvc/js/jquery-1.7.1.min.js"></script>--%>
    <%--CDN--%>
    <script src="http://code.jquery.com/jquery-1.12.0.min.js"></script>
</head>
<body>
<script>
    //页面加载完毕
    $(function () {
        //点击按钮,发送post请求,传递json参数
        $("#btn").click(function () {
            $.ajax({
                //设置请求类型
                type: 'post',
                //请求路径
                url: 'http://localhost:8080/hellospringmvc/json',
                //传递json参数
                data: '{"id":3,"name":"池傲天","age":21,"ride":"DeathDragon"}',
                //指定参数类型(如果json参数格式,必须设置为json类型)
                contentType: 'application/json;charset=utf-8',
                //该方法接收后台返回的数据格式
                dataType: 'json',
                //处理方法
                success: function (result) {
                    alert(result.id + '--' + result.name + '--' + result.age + '--' + result.ride);
                }

            });
        });
    });

</script>

<input type="button" value="Json字符串与Java对象转换" id="btn">
</body>
</html>

public class MercenaryUser {

    private Integer id;
    private String name;
    private Integer age;
    private String ride;
    //...
    }

hellospringmvc-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/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">
    <!-- 1.扫描Controller的包-->
    <context:component-scan base-package="com.self" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!-- 4.配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 4.1 页面前缀 -->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!-- 4.2 页面后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 3.开启mvc注解驱动-->
    <!-- 3.创建处理器适配器和处理器映射器-->
    <!--在Spring中一般采用@RequestMapping注解来完成映射关系,
    要想使@RequestMapping注解生效必须向上下文中注册DefaultAnnotationHandlerMapping和一个AnnotationMethodHandlerAdapter实例,
    这两个实例分别在类级别和方法级别处理。而<mvc:annotation-driven/>配置帮助我们自动完成上述两个实例的注入。
    -->
    <mvc:annotation-driven/>

    <!--静态资源处理-->
    <!--该标签相当于一次帮助我们把所有静态资源目录的文件对外映射出去。-->
    <mvc:default-servlet-handler/>
</beans>

输出:

前端发送的数据:{"age":21,"id":3,"name":"池傲天","ride":"DeathDragon"}

报错:

1、jquery方法执行不了。json请求页面无响应。

A:首先要引入jquery-1.7.1.min.js 文件,放到项目目录上,并通过从项目名开始的路径引用,或者全路径,从主机路径开始,否则会找不到js文件,因为我们没有设置相对路径。
<%--使用jQuery实现ajax异步请求后端Controller,同时发送Json字符串对象--%>
    <script src="/hellospringmvc/js/jquery-1.7.1.min.js"></script>

2、发起ajax请求时415报错——不支持的媒体类型(Unsupported media type)

jquery-1.7.1.min.js:2660 POST http://localhost:8080/hellospringmvc/json 415 ()

A:我们需要一:在hellospringmvc-servlet.xml中配置开启mvc注解驱动,该配置会创建处理器适配器和处理器映射器,自动注册DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter两个bean。
AnnotationMethodHandlerAdapter将会初始化7个转换器,可以通过调用AnnotationMethodHandlerAdapter的getMessageConverts()方法来获取转换器的一个集合 List<HttpMessageConverter>。
ByteArrayHttpMessageConverter
StringHttpMessageConverter
ResourceHttpMessageConverter
SourceHttpMessageConverter
XmlAwareFormHttpMessageConverter
Jaxb2RootElementHttpMessageConverter
MappingJacksonHttpMessageConverter
对于json的解析就是通过MappingJacksonHttpMessageConverter转换器完成的。
二:依赖jackson包,这样才能通过转换器+jackson解析json数据,@RequestBody需要。
    <!-- jackson支持包 -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.9.5</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.9.5</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.5</version>
    </dependency>

如果还是报415错误,那可能是因为在hellospringmvc-servlet.xml显式配置的处理器和适配器,导致转换器没有初始化出来。要注释掉。如下:

<!--2.创建RequestMappingHandlerMapping-->
<!--<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>-->

<!--3.创建RequestMappingHandlerAdapter-->
<!--<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>-->

参考:使用@RequestBody注解处理json时,报出HTTP Status 415的解决方案

注意:在hellospringmvc-servlet.xml配置中,当我们配置了静态资源处理时,就一定要开启mvc注解驱动,否则我们请求的处理都会被静态资源处理器处理了,导致请求的页面报404请求资源不存在。因此在hellospringmvc-servlet.xml配置中,两个都是一定要配置的,缺一不可。

<!-- 3.开启mvc注解驱动-->
    <!-- 3.创建处理器适配器和处理器映射器-->
    <!--在Spring中一般采用@RequestMapping注解来完成映射关系,
    要想使@RequestMapping注解生效必须向上下文中注册DefaultAnnotationHandlerMapping和一个AnnotationMethodHandlerAdapter实例,
    这两个实例分别在类级别和方法级别处理。而<mvc:annotation-driven/>配置帮助我们自动完成上述两个实例的注入。
    -->
    <mvc:annotation-driven/>

    <!--静态资源处理-->
    <!--该标签相当于一次帮助我们把所有静态资源目录的文件对外映射出去。-->
    <mvc:default-servlet-handler/>

Spring MVC RESTful风格开发

什么是RESTful风格?

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

简单地说:使用RESTful风格可以让我们客户端与服务端访问的URL更加简洁方便!

以下给出两个例子,前者没有采用RESTful风格,后者采用RESTful风格。

没有采用RESTful风格的URL:

采用RESTful风格的URL:

对比发现,RESTful风格更加简洁,RESTful主要依靠不同的请求方法来区分不同操作类型,这个与传统URL最大的区别。

Spring MVC开发RESTful应用

前置条件

为了在前端模拟出不同的请求方式,需要在web.xml引入SpringMVC提供的HiddenHttpMethodFilter,这是个配置请求方式转换过滤器。

<!-- 转换请求方式的过滤器 -->
<filter>
	<filter-name>hiddenHttpMethodFilter</filter-name>
	<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>hiddenHttpMethodFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

restful.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Spring MVC进行RESTful风格开发</title>
</head>
<body>

<!--增加 -->
<form action="/hellospringmvc/rest" method="post">
    <input type="submit" value="增加">
</form>

<!--查询 -->
<form action="/hellospringmvc/rest" method="get">
    <input type="submit" value="查询">
</form>

<!--修改 -->
<form action="/hellospringmvc/rest/8" method="post">
    <input type="hidden" name="_method" value="put">
    <input type="submit" value="修改">
</form>

<!--删除 -->
<form action="/hellospringmvc/rest/9" method="post">
    <input type="hidden" name="_method" value="delete">
    <input type="submit" value="删除">
</form>
</body>
</html>

@Controller
@RequestMapping("/rest")
public class RestfulController {

    @RequestMapping(method = RequestMethod.POST)
    @ResponseBody
    public String save() {
        System.out.println("增加...");
        return "success";
    }

    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public String get() {
        System.out.println("查询...");
        return "success";
    }

 /*   @RequestMapping(value = "/{id}",method = RequestMethod.POST)
    @ResponseBody
    public String update(@PathVariable("id") Integer id){
        System.out.println("修改...id="+id);
        return "success";
    }*/

    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    @ResponseBody
    public String update(@PathVariable("id") Integer id) {
        System.out.println("修改...id=" + id);
        return "success";
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable("id") Integer id) {
        System.out.println("删除...id=" + id);
        return "success";
    }
}

注意点:

  1. 为了接收RESTful请求,使用method属性指定对应的请求方式
  2. 使用@PathVariable注解接收RESTful请求的参数
  3. RESTful风格的请求处理完毕后,通常@ResponseBody注解把数据转换为Json返回,尽量不要转发或重定向页面,因为页面不支持PUT和DELETE请求。

请求:http://localhost:8080/hellospringmvc/restful.jsp

输出:

增加...
查询...
修改...id=8
删除...id=9

报错:如果不配置转换请求方式的过滤器HiddenHttpMethodFilter,请求会报错。

Spring MVC 文件上传

文件上传是表现层常见的需求,在Spring MVC中底层使用Apache的Commons FileUpload工具来完成文件上传,对其进行封装,让开发者使用起来更加方便。

文件上传需要:

  • 导入common-fileupload包
  • 配置文件解析器

注意:

  1. 必须配置CommonsMultipartResolver解析器
  2. 该解析器的id必须叫multipartResolver,否则无法成功接收文件
  3. 可以通过maxUploadSize属性限制文件上传大小

上传表单注意:

  1. 表单的enctype必须改为multipart/form-data
  2. 表单提交方式必须为POST,不能是GET

注意:

这里使用MultipartFile对象接收文件,并把文件存放在项目的upload目录下,同时还接收了普通参数。

示例:

pom.xml

<!-- commons-fileUpload -->
<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.3.1</version>
</dependency>

hellospringmvc-servlet.xml

<!-- 配置文件上传解析器 注意:必须配置id,且名称必须为multipartResolver-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 配置限制文件上传大小 (字节为单位)-->
    <property name="maxUploadSize" value="1024000"/>
</bean>

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Hello Spring MVP</title>
</head>
<body>
<h1>上传成功!</h1>
</body>
</html>

upload.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>文件上传</title>
</head>
<body>
<h3>SpringMVC方式文件上传</h3>
<form action="/hellospringmvc/upload" method="post" enctype="multipart/form-data">
    选择文件:<input type="file" name="fileName"> <br/>
    文件描述:<input type="text" name="desc"> <br/>
    <input type="submit" value="上传">
</form>
</body>
</html>

@Controller
public class UploadController {

    @RequestMapping("/upload")
    public String upload(HttpServletRequest request, MultipartFile fileName, String desc) {
        //1.获取网站的upload目录的路径: ServletContext对象
        //F:\self\hellospringmvc\target\hellospringmvc-1.0-SNAPSHOT\upload
        String upload = request.getSession().getServletContext().getRealPath("upload");
        ///F:/self/hellospringmvc/target/hellospringmvc-1.0-SNAPSHOT/WEB-INF/classes//upload
        String upload2 = UploadController.class.getResource("/").getFile()+"/upload";
        //判断该目录是否存在,不存在,自己创建
        File file = new File(upload);
        if (!file.exists()) {
            file.mkdir();
        }
        //把文件保存到upload目录
        //2.1 原来的文件名
        String originalFilename = fileName.getOriginalFilename();
        //2.生成随机文件名称
        String uuId = UUID.randomUUID().toString();
        //2.3 获取文件后缀 如 .jpg
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        //2.4 最终的文件名
        String newName = uuId + suffix;
        //3.保存
        try {
            //目标文件传入地址路径+名称
            fileName.transferTo(new File(upload + "/" + newName));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("文件描述:" + desc);
        //跳转到上传成功页面
        return "success";
    }
}

请求:http://localhost:8080/hellospringmvc/upload.jsp

输出:上传成功。

Spring MVC 文件下载

准备下载的文件

示例:

download.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>文件下载</title>
</head>
<body>
<h3>SpringMVC文件下载</h3>
<a href="/hellospringmvc/download">下载</a>
</body>
</html>

@Controller
public class DownloadController {

    @RequestMapping("/download")
    public void download(HttpSession session, HttpServletRequest request, HttpServletResponse response) throws IOException {
        //InputStream inputStream = session.getServletContext().getResourceAsStream("/download/template.gif");
        InputStream inputStream = request.getSession().getServletContext().getResourceAsStream("/download/template.gif");
        //2.输出文件
        //设置响应头
        response.setHeader("Content-Disposition","attachment;filename=template.gif");
        OutputStream outputStream = response.getOutputStream();
        byte[] buff = new byte[1024];
        int lenth = 0;
        while ((lenth= inputStream.read(buff))!= -1){
            outputStream.write(buff,0,lenth);
        }
        //3.关闭资源
        outputStream.close();
        inputStream.close();
    }
}

请求:http://localhost:8080/hellospringmvc/download.jsp

Spring MVC 拦截器

Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录,session是否超时等。

要使用Spring MVC中的拦截器,就需要对拦截器类进行定义和配置。

通常拦截器类可以通过两种方式来定义:

  1. 通过实现HandlerInterceptor接口
  2. 继承HandlerInterceptor接口的实现类(如:HandlerInterceptorAdapter)来定义。

注意:拦截器配置的顺序决定了拦截器的执行顺序,先配置会先被执行!

注意:一个拦截器和多个拦截器的执行顺序看下图。

一个拦截器的执行顺序:

多个拦截器的执行顺序:

示例:

@Controller
@RequestMapping("/say")
public class HelloController {
   @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String sayHello() {
        //System.out.println("----------HelloController-----sayHello---------------");
        System.out.println("3.目标控制器-HelloController");
        return "success";
    }
  }

public class FirstInterceptor implements HandlerInterceptor {
    /**
     *preHandle: 在控制器(目标)的方法之前被执行
     *   返回值:控制afterCompletion方法是否被执行
     *       true: 执行afterCompletion
     *       false: 不执行afterCompletion
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("1.FirstInterceptor的preHandle");
        return true;
    }

    /**
     * postHandle: 在控制器(目标)的方法成功执行完成之后(注意:控制器方法失败不执行)
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("5.FirstInterceptor的postHandle");
    }
    /**
     *  afterCompletion: 在执行完前面所有(拦截器和目标)的方法之后执行(注意: 不管控制器方法执行成功与否都会被执行 )
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("7.FirstInterceptor的afterCompletion");
    }
}

public class SecondInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("2.SecondInterceptor的preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("4.SecondInterceptor的postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("6.SecondInterceptor的afterCompletion");
    }
}

hellospringmvc-servlet.xml

<!--配置拦截器 :拦截器配置的顺序决定了拦截器的执行顺序,先配置会先被执行!-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--要拦截请求路径-->
        <mvc:mapping path="/**/*"/>
        <bean class="com.self.interceptor.FirstInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/say/hello"/>
        <!--如果只这么写就是只拦截路径为/hello的,上面的/say/hello是不会被拦截的-->
        <mvc:mapping path="/hello"/>
        <bean class="com.self.interceptor.SecondInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

请求:http://localhost:8080/hellospringmvc/say/hello

输出:

1.FirstInterceptor的preHandle
2.SecondInterceptor的preHandle
3.目标控制器-HelloController
4.SecondInterceptor的postHandle
5.FirstInterceptor的postHandle
6.SecondInterceptor的afterCompletion
7.FirstInterceptor的afterCompletion

登录超时拦截器:

public class RequestSessionTimeOutInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try{
            String uri = request.getRequestURI();
            if(uri == null){
                return true;
            }
             HttpSession session = request.getSession();
            if (null == session) {
                //401:HTTP401错误代表用户没有访问权限,需要进行身份认证
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
                return false;
            }
            return true;
        } catch (Exception e){
            //异常情况不拦截
            logger.error("拦截器配置失败",e);
            return true;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

Spring MVC 异常处理机制

在控制器的方法发生异常后,默认情况会显示Tomcat的500页面,这种用户体验并不好。如果我们在每个控制器方法自行捕获异常,显然又太繁琐。有没有好的异常处理办法呢?有的,就是Spring MVC的全局异常处理机制。Spring MVC提供了两种全局异常处理机制:

  1. 定义异常类,实现HandlerExceptionResolver接口
  2. 定义异常类,使用@ControllerAdvice+@ExceptionHandler注解

编写全局异常处理类

全局异常类编写方式一

实现HandlerExceptionResolver接口,然后实现resolveException方法,编写处理异常逻辑。

示例:

@Controller
@RequestMapping("/say")
public class HelloController {

    @RequestMapping(value = "/error")
    public String error() {
        int i = 100/0;
        return "success";
    }
  }

public class SimpleHandlerExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelAndView mv = new ModelAndView("error");
        mv.addObject("errorMsg",ex.getMessage());
        return mv;
    }
}

hellospringmvc-servlet.xml

<!--创建自定义异常处理对象-->
<bean class="com.self.exceptionhandler.SimpleHandlerExceptionResolver"/>

全局异常类编写方式二

直接在类上使用@ControllerAdvice,在异常处理方法上添加@ExceptionHandler注解。这种做法底层是AOP思想。

示例:

@ControllerAdvice
public class SimpleExceptionHandler {

    @ExceptionHandler
    public ModelAndView handlerException(Exception e){
        ModelAndView mv = new ModelAndView();
        mv.setViewName("error");
        mv.addObject("errorMsg",e.getMessage());
        return mv;
    }
}

hellospringmvc-servlet.xml

<!--创建自定义异常处理对象-->
<bean class="com.self.exceptionhandler.SimpleExceptionHandler"/>

请求:http://localhost:8080/hellospringmvc/say/error

Spring MVC 异常处理机制没处理前:

HTTP Status 500 - Request processing failed; nested exception is java.lang.ArithmeticException: / by zero

处理后:

注意:如果两种都配置了,会被面向切面先执行返回了。类上使用@ControllerAdvice,在异常处理方法上添加@ExceptionHandler注解。这种做法底层是AOP思想。

Spring MVC 表单数据验证

Spring MVC提供了表单数据验证功能 。

前提:导入数据验证依赖包。

表单数据验证的重点是在Pojo类使用相关注解指定每个属性的验证规则。以下为可以使用的注解:

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

在Controller中,我们需要判断Pojo是否产生了验证错误信息,如果有的话,则把信息转给JSP页面显示。

示例:

pom.xml

<!-- 验证器所需的包 -->
<dependency>
  <groupId>com.fasterxml</groupId>
  <artifactId>classmate</artifactId>
  <version>1.4.0</version>
</dependency>

<dependency>
  <groupId>org.jboss.logging</groupId>
  <artifactId>jboss-logging</artifactId>
  <version>3.3.2.Final</version>
</dependency>

<dependency>
  <groupId>org.hibernate.validator</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>6.0.13.Final</version>
</dependency>

<dependency>
  <groupId>javax.validation</groupId>
  <artifactId>validation-api</artifactId>
  <version>2.0.1.Final</version>
</dependency>

public class User {
    private Integer id;
    @NotNull
    @Pattern(regexp = "^([a-zA-Z]*[0-9_-]*$)", message = "只能包含字母、数字、下划线,且不能以数字或下划线开头")
    @Size(min = 1, max = 110)
    private String name;
    @NotNull
    @Range(min = 1,max = 100,message = "年龄不在合法范围内")
    private Integer age;
    @Pattern(regexp = "^([a-zA-Z]*$)", message = "只能包含字母")
    private String ride;
    //...
    }

@Controller
public class ValidateController {

    @RequestMapping("/check")
    public String check(@Valid User user, BindingResult result, Model model) {
        //如果表单数据验证有异常
        if (result.hasErrors()) {
            //取出所有失败信息
            List<FieldError> fieldErrors = result.getFieldErrors();
            for (FieldError fieldError : fieldErrors) {
                //把错误信息存入request域,传递到JSP页面显示
                model.addAttribute("ERROR_" + fieldError.getField(), fieldError.getDefaultMessage());
            }
            return "forward:validate.jsp";
        }

        System.out.println("User=" + JSON.toJSONString(user));
        return "success";
    }
}

validate.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>表单数据验证</title>

</head>
<body>
<form action="/hellospringmvc/check" method="post">
    用户名:<input type="text" name="name">${ERROR_name}<br/>
    年龄:<input type="text" name="age">${ERROR_age}<br/>
    坐骑:<input type="text" name="ride">${ERROR_ride}<br/>
    <input type="submit" value="提交">
</form>
</body>
</html>

请求:http://localhost:8080/hellospringmvc/validate.jsp

输出:

Maven单模块SSM整合

本文讲解使用Maven单模块方式进行Spring MVC+Spring+MyBatis整合。为了把整合步骤体现地更加清晰,我们可以把步骤分为以下六个部分:

  1. 准备数据库环境
  2. 单独搭建Spring环境
  3. 单独搭建Spring MVC环境
  4. Spring整合Spring MVC
  5. 单独搭建MyBatis环境
  6. MyBatis整合Spring

1、准备数据库环境

CREATE TABLE `t_user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(64) NOT NULL COMMENT '姓名',
  `dept` varchar(254) NOT NULL COMMENT '部门',
  `phone` varchar(16) NOT NULL COMMENT '电话',
  `height` decimal(10,2) DEFAULT NULL COMMENT '身高',
  `create_emp` bigint(20) NOT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `modify_emp` bigint(20) DEFAULT NULL COMMENT '修改人',
  `modify_time` datetime DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='用户表';

2、单独搭建Spring环境

创建Web项目——使用原来的hellospringmvc

SSM相关依赖

pom.xml

<?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>com.self</groupId>
  <artifactId>hellospringmvc</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.7</source>
          <target>1.7</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <!--
  SSM整合的基础依赖
-->
  <!-- 1.spring相关的依赖 -->
  <dependencies>
    <!-- 1.1 ioc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.2.RELEASE</version>
    </dependency>
    <!--1.2 aop -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.7</version>
    </dependency>
    <!-- 1.3 声明式事务-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.0.2.RELEASE</version>
    </dependency>
    <!-- 1.4 test -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
    </dependency>


    <!-- 2. mybatis相关依赖 -->
    <!-- 2.1 mysql驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.46</version>
    </dependency>
    <!-- 2.2 数据源 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.6</version>
    </dependency>
    <!-- 2.3 mybatis核心包 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.5</version>
    </dependency>
    <!-- 3. springmvc相关依赖-->
    <!-- 3.1 springmvc核心包 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.0.2.RELEASE</version>
    </dependency>
    <!--3.2 servlet依赖 -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <!--3.3 jstl标签库-->
    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <!-- 4. log4j日志 -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.2</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-core</artifactId>
      <version>1.2.3</version>
    </dependency>

    <!-- 5. spring与mybatis整合包 *** -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.29</version>
    </dependency>
    <!-- jackson支持包 -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.9.5</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.9.5</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.5</version>
    </dependency>

    <!-- 验证器所需的包 -->
    <dependency>
      <groupId>com.fasterxml</groupId>
      <artifactId>classmate</artifactId>
      <version>1.4.0</version>
    </dependency>

    <dependency>
      <groupId>org.jboss.logging</groupId>
      <artifactId>jboss-logging</artifactId>
      <version>3.3.2.Final</version>
    </dependency>

    <dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.0.13.Final</version>
    </dependency>

    <dependency>
      <groupId>javax.validation</groupId>
      <artifactId>validation-api</artifactId>
      <version>2.0.1.Final</version>
    </dependency>
  </dependencies>
</project>

设计Pojo

public class User {

    /**
     * id
     */
    private int id;
    /**
     * 名字
     */
    private String name;
    /**
     * 部门,帝国
     */
    private String dept;
    /**
     * 联系号码
     */
    private String phone;
    /**
     * 身高
     */
    private BigDecimal height;
    /**
     * 创建人
     */
    private Long createEmp;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 修改人
     */
    private Long modifyEmp;
    /**
     * 修改时间
     */
    private Date modifyTime;
    //...
    }

编写业务接口和实现

public interface UserService {

    List<User> getALlUsers();
}

//给业务实现类加入@Service注解,目的是把该对象放入Spring IOC容器。
@Service("userService")
//@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
    @Override
    public List<User> getALlUsers() {
        logger.error("查询所有用户成员...");
        return userMapper.getALlUsers();
    }
}

编写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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/tx
             http://www.springframework.org/schema/tx/spring-tx.xsd
              http://www.springframework.org/schema/aop
              http://www.springframework.org/schema/aop/spring-aop.xsd
                http://www.springframework.org/schema/context
              http://www.springframework.org/schema/context/spring-context.xsd">
    <!--spring 容器扫描配置-->
    <context:component-scan base-package="com.self">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>

Spring环境单元测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest {

    @Autowired
    private UserService userService;

    @Test
    public void test() {
        userService.getALlUsers();
    }

}

输出表示spring环境搭建成功:

查询所有用户成员...

3、 单独搭建Spring MVC环境

Spring MVC核心控制器web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://java.sun.com/xml/ns/javaee"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
       version="2.5">
   <!-- 配置核心控制器 :DispatcherServlet -->
   <servlet>
      <servlet-name>hellospringmvc</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <!-- springmvc配置文件加载路径
             1)默认情况下,读取WEB-INF下面的default-servlet.xml文件
             2)可以改为加载类路径下(resources目录),加上classpath:
         -->
      <init-param>
         <param-name>contextConfigLocation</param-name>
         <param-value>WEB-INF/hellospringmvc-servlet.xml</param-value>
         <!--<param-value>WEB-INF/simple-servlet.xml</param-value>-->
      </init-param>
      <!--init-param必须放在load-on-startup前,否则会报错:invalid content was found starting with element 'init-param'. One of '{"http://java.sun.com/xml/ns/javaee":run-as, "http://java.sun.com/xml/ns/javaee":security-role-ref}' is expected-->
      <!--
           DispatcherServlet对象创建时间问题
              1)默认情况下,第一次访问该Servlet时创建对象,默认是访问时创建,意味着在这个时间才去加载hellospringmvc-servlet.xml
              2)可以改变为在项目启动时候就创建该Servlet,提高用户访问体验。
                  <load-on-startup>1</load-on-startup>
                        数值越大,对象创建优先级越低! (数值越低,越先创建)
        -->
      <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
      <servlet-name>hellospringmvc</servlet-name>
      <url-pattern>/</url-pattern>
   </servlet-mapping>
   <!--重新配置Tomcat的DefaultServlet的映射路径-->
   <servlet-mapping>
      <servlet-name>default</servlet-name>
      <url-pattern>*.html</url-pattern>
      <url-pattern>*.jpg</url-pattern>
      <url-pattern>*.css</url-pattern>
      <url-pattern>*.js</url-pattern>
      <url-pattern>*.png</url-pattern>
   </servlet-mapping>
</web-app>

springmvc配置——hellospringmvc-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/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">
    <!-- 1.扫描Controller的包-->
    <context:component-scan base-package="com.self" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <!-- 2.配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 2.1 页面前缀 -->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!-- 2.2 页面后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 3.开启mvc注解驱动-->
    <mvc:annotation-driven/>
</beans>

编写UserController

@RequestMapping("/user")
@Controller
public class UserController {
    /**
     * 查询所有用户
     */
    @RequestMapping("list")
    public String showAll(Model model) {
        List<User> users = userService.getALlUsers();
        //存入数据到request域
        model.addAttribute("list","用户数据");
        //返回list.jsp页面
        return "userList";
    }
}

userList.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>显示用户数据</title>
</head>
<body>
${list}
</body>
</html>

项目部署到Tomcat

请求:http://localhost:8080/user/list

4 、Spring整合Spring MVC

配置Spring监听器

Spring和Spring MVC融合使用,只要在web.xml配置监听器,在项目启动的时候,加载applicationContext.xml文件,把Spring环境启动即可。

web.xml

<!-- 配置spring监听器,用于加载applicationContext.xml(初始化SpringIOC容器) -->
<context-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>classpath*:applicationContext.xml</param-value>
</context-param>
<listener>
   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

在控制层调用业务层,如果Controller成功注入Service,代表Spring与Spring MVC整合成功!

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

    @Autowired
    private UserService userService;
    /**
     * 查询所有用户
     */
    @RequestMapping("list")
    public String showAll(Model model) {
        List<User> users = userService.getALlUsers();
        //存入数据到request域
        model.addAttribute("list","用户数据");
        //返回list.jsp页面
        return "userList";
    }
}

输出:

5、 单独搭建MyBatis环境

编写UserDao接口

@Repository
public interface UserMapper {

    List<User> getALlUsers();
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.self.dao.UserMapper">

    <select id="getALlUsers" resultType="User">
        select u.create_time createTime,u.id id ,u.name name ,u.dept dept,u.phone phone from `t_user` u where 1 = 1
    </select>
</mapper>

mybatis-config.xml——该文件是MyBatis核心配置,里面配置数据源及Dao接口映射等信息。

<?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>
    <!-- 读取jdbc.properties -->
    <properties resource="jdbc.properties"/>

    <!--1.别名扫描 -->
    <typeAliases>
        <package name="com.self.pojo"/>
    </typeAliases>

    <!--2.数据库连接 -->
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="jdbc"></transactionManager>
            <dataSource type="pooled">
                <property name="url" value="${jdbc.url}"/>
                <property name="driver" value="${jdbc.driver}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--3.映射关联 -->
    <mappers>
        <package name="com.self.dao"/>
    </mappers>

</configuration>

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/hello_mybatis?characterEncoding=utf8
jdbc.username=root
jdbc.password=123456
jdbc.initialSize=3
jdbc.maxActive=10

MyBatis测试类

public class MabatisTest {

    @Test
    public void testFindAll() throws IOException {

        //1.加载SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");

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

        //3.创建SqlSession
        SqlSession sqlSession = factory.openSession();

        //4.创建Dao代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        //5.执行方法
        List<User> list = userMapper.getALlUsers();
        System.out.println(JSON.toJSONString(list));
        //6.释放资源
        sqlSession.close();
        in.close();

    }
}

输出:

[{"createTime":1585635455000,"dept":"amy empire","id":1,"name":"大青山","phone":"123456"},{"createTime":1585635455000,"dept":"amy empire","id":2,"name":"艾米哈珀","phone":"123456"},{"createTime":1585635455000,"dept":"amy empire","id":3,"name":"池寒枫","phone":"123456"},{"createTime":1585647970000,"dept":"森林矮人王国","id":4,"name":"霍恩斯","phone":"852-253521"}]

参考代码位置:

6 、MyBatis整合Spring

整合的思路是:Spring依赖IOC容器创建MyBatis所需要的SqlSessionFactory,从而利用SqlSessionFactory完成Dao层的操作。

注意:

因为Spring已经把之前MyBatis的数据源及Dao映射等信息都集成了,所以MyBatis的mybatis-config.xml已经不需要啦,可以删除。

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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/tx
             http://www.springframework.org/schema/tx/spring-tx.xsd
              http://www.springframework.org/schema/aop
              http://www.springframework.org/schema/aop/spring-aop.xsd
                http://www.springframework.org/schema/context
              http://www.springframework.org/schema/context/spring-context.xsd">
    <!--spring 容器扫描配置-->
    <context:component-scan base-package="com.self">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--载入properties-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 1. 创建数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 2. 为了创建Dao代理对象,先创建SqlSessionFactory对象 -->
    <!--  SqlSessionFactoryBean: 创建SqlSessionFactory对象的工具 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
        <!--typeAliasesPackage:批量别名处理 通过这些property就可以把mybatis-config.xml替代掉了-->
        <property name="typeAliasesPackage" value="com.self.pojo"/>
        <!-- 所有配置的mapper文件 该配置相当于是mybatis-config.xml里的mappers配置,在这边直接扫描获取了-->
        <!--<property name="mapperLocations" value="classpath*:com/self/dao/*.xml"/>-->
    </bean>

    <!-- 3. 扫描Dao接口所在包,扫描后用于创建Dao代理对象,把代理对象放入IOC容器 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- Dao扫描目录 -->
        <property name="basePackage" value="com.self.dao"/>
        <!--<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>-->
    </bean>
    <!-- 添加事务支持 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 注册事务管理驱动 表示支持声明式事务 @Transactional 注解标注的会被代理实现事务,但要用在有接口的public方法中-->
    <!--基于注解的方式使用事务配置声明-->
    <tx:annotation-driven transaction-manager="transactionManager" />
</beans>

业务层注入持久层对象

//给业务实现类加入@Service注解,目的是把该对象放入Spring IOC容器。
@Service("userService")
//@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
    @Override
    public List<User> getALlUsers() {
        logger.error("查询所有用户成员...");
        return userMapper.getALlUsers();
    }
}

编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = "classpath:applicationContext.xml")
public class SpringMyBatisTest {

    //从IOC容器中获取业务实现
    @Autowired
    private UserService userService;

    @Test
    public void testFindAll(){
        System.out.println(JSON.toJSONString(userService.getALlUsers()));
    }
}

SSM框架已经整合完成。剩下就是把数据显示到JSP页面上。

修改UserController类

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

    @Autowired
    private UserService userService;
    /**
     * 查询所有用户
     */
    @RequestMapping("list")
    public String showAll(Model model) {
        List<User> users = userService.getALlUsers();
        //存入数据到request域
        model.addAttribute("list", users);
        //model.addAttribute("list","用户数据");
        //返回list.jsp页面
        return "userList";
    }
}

修改JSP页面显示内容

userList.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>显示用户数据</title>
</head>
<body>
<h3>用户列表</h3>
<table border="1">
    <tr>
        <td>编号</td>
        <td>用户名</td>
        <td>帝国</td>
        <td>电话号码</td>
        <td>创建时间</td>
    </tr>
    <!--
    items: 需要需要遍历的集合
    var: 每个对象的变量名称
    -->
    <c:forEach items="${list}" var="user">
        <tr>
            <td>${user.id}</td>
            <td>${user.name}</td>
            <td>${user.dept}</td>
            <td>${user.phone}</td>
            <td>${user.createTime}</td>
        </tr>
    </c:forEach>
</table>
</body>
</html>

输出:

报错:

1、在通过controller请求到底层service时报错:springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.self.service.UserService'。

A:这种情况下一般是UserService没有注册到spring容器中,一般分两种情况,要么扫描bean时没有扫到,所以没添加,要嘛是UserService实现类(注意不是UserService接口)没有配置@Service,所以spring没有把他当成一个组件注册到容器中。还有就是在配置UserService实现类时配置@Service错误,要指定名称,否则默认是类名首字母小写后的全称,因此会找不到bean。

还有就是web.xml缺少ContextLoaderListener配置,导致spring容器里的bean没有被加载。

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

2、启动项目时无法启动成功,userService注入不了dao依赖。

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService': Unsatisfied dependency expressed through field 'userMapper'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userMapper' defined in file [F:\practice\hellospringmvc\target\hellospringmvc-1.0-SNAPSHOT\WEB-INF\classes\com\self\dao\UserMapper.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. Cause: org.apache.ibatis.builder.BuilderException: Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'User'.  Cause: java.lang.ClassNotFoundException: Cannot find class: User

 <!--A:这是由于UserMapper.xml的resultType="User",没有指定全路径,这时候要嘛指定全路径,要嘛在applicationContext.xml配置sqlSessionFactory时批量别名处理。
如下: -->
    <!-- 2. 为了创建Dao代理对象,先创建SqlSessionFactory对象 -->
    <!--  SqlSessionFactoryBean: 创建SqlSessionFactory对象的工具 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
        <!--typeAliasesPackage:批量别名处理 通过这些property就可以把mybatis-config.xml替代掉了-->
        <property name="typeAliasesPackage" value="com.self.pojo"/>
    </bean>

Maven多模块SSM整合

一些中大型项目,我希望采用Maven多模块构建方式来搭建SSM整合项目。

Maven多模块构建SSH项目架构图:

示例:

新建一个noodle-parent的project工程。子项目通过右击项目名创建新的maven依赖模块。

整体结构:

1、建立parent工程

配置父工程noodle-parent——pom.xml

<?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>com.self</groupId>
    <artifactId>noodle-parent</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>noodle-pojo</module>
        <module>noodle-dao</module>
        <module>noodle-service</module>
        <module>noodle-web</module>
    </modules>

    <!--
SSM整合的基础依赖
-->
    <!-- 1.spring相关的依赖 -->
    <dependencies>
        <!-- 1.1 ioc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--1.2 aop -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
        <!-- 1.3 声明式事务-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!-- 1.4 test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>


        <!-- 2. mybatis相关依赖 -->
        <!-- 2.1 mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <!-- 2.2 数据源 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.6</version>
        </dependency>
        <!-- 2.3 mybatis核心包 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>

        <!-- 3. springmvc相关依赖-->
        <!-- 3.1 springmvc核心包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--3.2 servlet依赖 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
        <!--3.3 jstl标签库-->
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <!-- 4. log4j日志 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <!-- 5. spring与mybatis整合包 *** -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.1</version>
        </dependency>
    </dependencies>
</project>

2、建立pojo工程

编写Pojo类

public class User {

    /**
     * id
     */
    private int id;
    /**
     * 名字
     */
    private String name;
    /**
     * 部门,帝国
     */
    private String dept;
    /**
     * 联系号码
     */
    private String phone;
    /**
     * 身高
     */
    private BigDecimal height;
    /**
     * 创建人
     */
    private Long createEmp;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 修改人
     */
    private Long modifyEmp;
    /**
     * 修改时间
     */
    private Date modifyTime;
    //...
    }

3、建立dao工程

依赖domain工程

<dependencies>
    <dependency>
        <groupId>com.self</groupId>
        <artifactId>noodle-pojo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

public interface UserMapper {

    List<User> getALlUsers();
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.self.dao.UserMapper">

    <select id="getALlUsers" resultType="User">
        select u.create_time createTime,u.id id ,u.name name ,u.dept dept,u.phone phone from `t_user` u where 1 = 1
    </select>
</mapper>

编写Spring的Dao配置

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/hello_mybatis?characterEncoding=utf8
jdbc.username=root
jdbc.password=123456

applicationContext-dao.xml文件只存放与Dao有关的配置 。

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/tx
             http://www.springframework.org/schema/tx/spring-tx.xsd
              http://www.springframework.org/schema/aop
              http://www.springframework.org/schema/aop/spring-aop.xsd
                http://www.springframework.org/schema/context
              http://www.springframework.org/schema/context/spring-context.xsd">
    <!--载入properties-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 1. 创建数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 2. 为了创建Dao代理对象,先创建SqlSessionFactory对象 -->
    <!--  SqlSessionFactoryBean: 创建SqlSessionFactory对象的工具 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
        <!--typeAliasesPackage:批量别名处理 通过这些property就可以把mybatis-config.xml替代掉了-->
        <property name="typeAliasesPackage" value="com.self.pojo"/>
        <!-- 所有配置的mapper文件 该配置相当于是mybatis-config.xml里的mappers配置,在这边直接扫描获取了-->
        <!--<property name="mapperLocations" value="classpath*:com/self/dao/*.xml"/>-->
    </bean>

    <!-- 3. 扫描Dao接口所在包,扫描后用于创建Dao代理对象,把代理对象放入IOC容器 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- Dao扫描目录 -->
        <property name="basePackage" value="com.self.dao"/>
        <!--<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>-->
    </bean>
</beans>

4、建立Service工程

依赖Dao工程 pom.xml

<?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">
    <parent>
        <artifactId>noodle-parent</artifactId>
        <groupId>com.self</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>noodle-service</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.self</groupId>
            <artifactId>noodle-dao</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

public interface UserService {

    List<User> getALlUsers();
}

//给业务实现类加入@Service注解,目的是把该对象放入Spring IOC容器。
@Service("userService")
//@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> getALlUsers() {
        System.out.println("查询所有用户成员...");
        return userMapper.getALlUsers();
    }
}

编写Spring的Service配置

applicationContext-service.xml

该配置主要需要扫描Service实现类和配置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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/tx
             http://www.springframework.org/schema/tx/spring-tx.xsd
              http://www.springframework.org/schema/aop
              http://www.springframework.org/schema/aop/spring-aop.xsd
                http://www.springframework.org/schema/context
              http://www.springframework.org/schema/context/spring-context.xsd">
    <!--spring 容器扫描配置-->
    <context:component-scan base-package="com.self">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--Spring声明式事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="get*" isolation="DEFAULT" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="load*" isolation="DEFAULT" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="select*" isolation="DEFAULT" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="find*" isolation="DEFAULT" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="*" isolation="DEFAULT" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!--事务切面-->
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="pt" expression="execution(* com.self.service.impl.*ServiceImpl.*(..))"/>
        <!--切面-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    </aop:config>
</beans>

5、建立Web工程——项目为Web项目

依赖Service工程 pom.xml

<?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">
    <parent>
        <artifactId>noodle-parent</artifactId>
        <groupId>com.self</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>war</packaging>

    <artifactId>noodle-web</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.self</groupId>
            <artifactId>noodle-service</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

配置监听器和核心控制器

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://java.sun.com/xml/ns/javaee"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
       version="2.5">
   <!-- 配置核心控制器 :DispatcherServlet -->
   <servlet>
      <servlet-name>dispatcherServlet</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <!-- springmvc配置文件加载路径
             1)默认情况下,读取WEB-INF下面的default-servlet.xml文件
             2)可以改为加载类路径下(resources目录),加上classpath:
         -->
      <init-param>
         <param-name>contextConfigLocation</param-name>
         <param-value>classpath:noodlespringmvc-servlet.xml</param-value>
         <!--<param-value>WEB-INF/simple-servlet.xml</param-value>-->
      </init-param>
      <!--init-param必须放在load-on-startup前,否则会报错:invalid content was found starting with element 'init-param'. One of '{"http://java.sun.com/xml/ns/javaee":run-as, "http://java.sun.com/xml/ns/javaee":security-role-ref}' is expected-->
      <!--
           DispatcherServlet对象创建时间问题
              1)默认情况下,第一次访问该Servlet时创建对象,默认是访问时创建,意味着在这个时间才去加载hellospringmvc-servlet.xml
              2)可以改变为在项目启动时候就创建该Servlet,提高用户访问体验。
                  <load-on-startup>1</load-on-startup>
                        数值越大,对象创建优先级越低! (数值越低,越先创建)
        -->
      <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
      <servlet-name>dispatcherServlet</servlet-name>
      <url-pattern>/</url-pattern>
   </servlet-mapping>
   <!--重新配置Tomcat的DefaultServlet的映射路径-->
   <servlet-mapping>
      <servlet-name>default</servlet-name>
      <url-pattern>*.html</url-pattern>
      <url-pattern>*.jpg</url-pattern>
      <url-pattern>*.css</url-pattern>
      <url-pattern>*.js</url-pattern>
      <url-pattern>*.png</url-pattern>
   </servlet-mapping>

   <!-- 配置spring监听器,用于加载applicationContext.xml(初始化SpringIOC容器) -->
   <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath*:applicationContext-*.xml</param-value>
   </context-param>
   <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
   </listener>

   <!-- 字符编码过滤器 -->
   <filter>
      <filter-name>encodingFilter</filter-name>
      <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
      <init-param>
         <param-name>encoding</param-name>
         <param-value>utf-8</param-value>
      </init-param>
   </filter>
   <filter-mapping>
      <filter-name>encodingFilter</filter-name>
      <url-pattern>/*</url-pattern>
   </filter-mapping>
</web-app>

注意:

Spring监听器读取的路径为classpath*:,这个语法指加载当前项目及依赖工程的所有符合条件的文件。因为applicationContext.xml分布在不同的Maven工程,所以必须使用该语法加载!

noodlespringmvc-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/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">
    <!-- 1.扫描Controller的包-->
    <context:component-scan base-package="com.self" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <!-- 2.配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 2.1 页面前缀 -->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!-- 2.2 页面后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 3.开启mvc注解驱动-->
    <!--不添加也能使用,高版本spring已经默认实现了。
在Spring中一般采用@RequestMapping注解来完成映射关系,要想使@RequestMapping注解生效必须向上下文中注册DefaultAnnotationHandlerMapping和一个AnnotationMethodHandlerAdapter实例,这两个实例分别在类级别和方法级别处理。而<mvc:annotation-driven/>配置帮助我们自动完成上述两个实例的注入。
    -->
    <mvc:annotation-driven/>
    <!--<mvc:default-servlet-handler/>-->
</beans>

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

    @Autowired
    private UserService userService;
    /**
     * 查询所有用户
     */
    @RequestMapping("/list")
    public String showAll(Model model) {
        List<User> users = userService.getALlUsers();
        //存入数据到request域
        model.addAttribute("list", users);
        //model.addAttribute("list","用户数据");
        //返回list.jsp页面
        return "userList";
    }
}

userList.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>显示用户数据</title>
</head>
<body>
<h3>用户列表</h3>
<table border="1">
    <tr>
        <td>编号</td>
        <td>用户名</td>
        <td>帝国</td>
        <td>电话号码</td>
        <td>创建时间</td>
    </tr>
    <!--
    items: 需要需要遍历的集合
    var: 每个对象的变量名称
    -->
    <c:forEach items="${list}" var="user">
        <tr>
            <td>${user.id}</td>
            <td>${user.name}</td>
            <td>${user.dept}</td>
            <td>${user.phone}</td>
            <td>${user.createTime}</td>
        </tr>
    </c:forEach>
</table>
</body>
</html>

输出:

注意:当我们配置项目到tomcat上时,在创建artifact时可以指定项目名称(不一定要是项目名)作为请求的路径,像这样接在localhost后面,http://localhost:8080/noodle/user/list。如果只是设置/ (斜杠)则直接在端口后面接请求地址。

疑问

Q:在springmvc容器的配置文件hellospringmvc-servlet.xml中配置开启mvc注解驱动的作用是什么?都作用在哪些地方?什么时候用到?

教程里的解释是在Spring中一般采用@RequestMapping注解来完成映射关系,要想使@RequestMapping注解生效必须向上下文中注册DefaultAnnotationHandlerMapping和一个AnnotationMethodHandlerAdapter实例,这两个实例分别在类级别和方法级别处理。而mvc:annotation-driven/配置帮助我们自动完成上述两个实例的注入。 但是在实践中我们就算没有显式注册这两个bean实例或者在spring mvc配置的hellospringmvc-servlet.xml中加上 mvc:annotation-driven/这句配置也不妨碍我们使用@RequestMapping注解来完成映射关系,为什么?

  <!-- 3.开启mvc注解驱动-->
    <mvc:annotation-driven/>

Q:什么叫Ant风格的路径匹配功能?

A: ANT通配符有以下三种:

通配符 说明
? 匹配任何单字符

  • 匹配0或者任意数量的字符
    

** 匹配0或者更多的目录

例子:

URL路径 说明
/app/.x 匹配(Matches)所有在app路径下的.x文件
/app/p?ttern 匹配(Matches) /app/pattern 和 /app/pXttern,但是不包括/app/pttern
/**/example 匹配(Matches) /app/example, /app/foo/example, 和 /example
/app/
/dir/file.匹配(Matches) /app/dir/file.jsp, /app/foo/dir/file.html,/app/foo/bar/dir/file.pdf, 和 /app/dir/file.java
/*/.jsp 匹配(Matches)任何的.jsp 文件

属性: 最长匹配原则(has more characters) 说明,URL请求/app/dir/file.jsp,现在存在两个路径匹配模式/*/.jsp和/app/dir/.jsp,那么会根据模式/app/dir/.jsp来匹配

参考

Q:RequestMappingHandlerMapping会被默认创建么?在什么情况下创建,标记有@RequestMapping("/hello") 注解时还是扫描Controller时?

A:是在web.xml配置mvc:annotation-driven/时。RequestMappingHandlerMapping和RequestMappingHandlerAdapter会默认注册。

当然,一般情况下我们是不需要配置mvc:annotation-driven/的,默认会注册,但当我们web.xml中配置了其他如BeanNameUrlHandlerMapping处理映射器时,就要加这句,否则不会默认帮我们注册,这个需要研究下代码是怎么个处理方式。

在不配置HandlerMapping 的情况下,容器默认会注册初始化BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping来处理映射。而HandlerAdapter会有三种,分别是HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter,RequestMappingHandlerAdapter。

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
   org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
   org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
   org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

注意:如果有在web.xml中配置指定的HandlerMapping 和 HandlerAdapter 的话,则只注册配置的处理器。

mvc:annotation-driven的作用

Spring 3.0.x中使用了mvc:annotation-driven后,默认会帮我们注册默认处理请求,参数和返回值的类,其中最主要的两个类:DefaultAnnotationHandlerMapping 和 AnnotationMethodHandlerAdapter ,分别为HandlerMapping的实现类和HandlerAdapter的实现类,从3.1.x版本开始对应实现类改为了RequestMappingHandlerMapping和RequestMappingHandlerAdapter。

mvc:annotation-driven的作用参考

Q:如果我们没有配置HandlerAdapter ,默认是创建什么类型的HandlerAdapter 来处理我们的Controller呢?

A:默认不配置SimpleControllerHandlerAdapter, 也能处理Controller类型为org.springframework.web.servlet.mvc.Controller的接口,SimpleControllerHandlerAdapter会默认注册.

Q:保存HandlerMethod类型的容器mappingLookup为什么要初始化为new LinkedHashMap<>();而不是HashMap<>()类型呢?出于什么考虑?LinkedHashMap和HashMap各种的使用场景和优势缺点是什么?

A:考虑动态添加的效率?

Q:转发和重定向的区别?

Q:在目前主流的三种 Web 服务交互方案中,REST 相比于SOAP(Simple Object Access protocol, 简单对象访问协议) 以及 XML-RPC 更加简单明了。了解下目前主流的Web 服务交互方案。

Q:什么叫 Payload 的编码?

Q:一般实践中的RESTful风格的请求开发是通过请求方式POST、GET等的不同来区分的么?

Q:ApplicationContext和WebApplicationContext有什么区别?用他们去getBean()是一样的?

A:Spirng容器只有一个,spring和springmvc的配置可以互相放在各自的配置xml中,最后都作用在spring容器中。待详细了解。

Q:为什么叫Spring MVC、MyBatis整合Spring?而不是反过来叫Spring MVC整合Spring呢?是随便叫还是有什么区别?

Q:ContextLoaderListener的作用?

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

Q:@Repository注解在mybatis中有什么用么?一般不都是通过扫包来获得dao对象么?

//不需要加注解
//@Repository
//@Mapper
public interface UserMapper {

    List<User> getALlUsers();
}

其他:

1、md删除线使用:

如果段落上的文字要添加删除线,只需要在文字的两端加上两个波浪线 ~~ 即可
~~shanchu.com~~

2、DispatcherServlet.properties ——DispatcherServlet初始化默认配置

org.springframework.web.servlet.DispatcherServlet#defaultStrategies  jdk1.8 284行


# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

3、IDEA的tomcat日志输出乱码问题的解决。

这是因为下载的tomcat8中\apache-tomcat-8.5.55\conf\logging.properties 配置默认编码是UTF-8.而Windows系统的默认编码格式是GBK,因此在对输出的字符打印时因为解码不对而导致的乱码。只要对logging.properties中的编码格式UTF-8配置注释掉即可。

参考

4、JDK1.8对接口的默认实现。

public interface HandlerInterceptor {
   default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception {

      return true;
   }
 }
05-15 01:44