SpringMVC学习笔记

  • SpringMVC并不像Spring一样是一个顶级项目,而是Spring Framework中的一个模块

  • 下面这些是顶级项目

  • 在Web中,都是Web客户端发起请求,Web服务器接收处理请求并产生响应。一般Web服务器是不能主动通知Web客户端更新内容。虽然有些技术可以帮我们实现这样的效果,如:服务器推技术(Comet)、还有HTML5中的websocket等

1、MVC、WebMVC、SpringMVC

  • Spring中的#{}表示从容器中获取,${}表示从外部文件获取

  • SSM:Spring + SpringMVC + MyBatis

1.1、MVC

  • 注意,MVC不是三层架构,而是一种架构模式

  • 表示层 UI、业务逻辑层 BLL、数据访问层 DAL

  • 三层架构分为:表现层、中间层、数据层。表现层分为:MVC

  • 在标准的MVC中,模型能主动推数据给视图进行更新,但在Web开发中模型是无法主动推给视图,因为在Web的访问是请求-响应的模式。必须由客户端主动发出请求后,服务器才能把数据返回

  • MVC(Model-View-Controller),是一种架构型的模式,本身不引入新功能,只是帮助我们将开发的代码结构,组织的更加合理。其中:

    • Model(模型) :负责提供要展示的数据,因此包含数据和行为,行为是用来处理这些数据的。不过现在一般都分离开来,例如分为Value Object(数据)和 服务层(行为)。也就是数据由实体类或者javabean来提供,行为由service层来提供。

    • View(视图) :负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西

    • Controller(控制器) :负责接收用户请求,委托给模型进行处理,处理完毕后把返回的模型数据交给给视图。也就是说控制器在中间起到一个调度的作用

1.2、WebMVC

  • Web中MVC里面的模型、视图、控制器的概念,和标准MVC概念是一样的。

  • 但是在Web MVC模式下,模型无法【主动】推数据给视图,如果用户想要视图更新,需要再发送一次请求。在我们之前的学习中:

    • 把Servlet作为Controller(控制器)

    • 把jsp作为View(视图)

    • 把javabean作为Model(模型)中的数据,service层作为Model(模型)中的行为

1.3、SpringMVC

  • SpringWebMVC简称SpringMVC,它是Spring Framework框架中提供的一个模块,通过实现MVC模式来很好地将数据、业务与展现进行分离

  • SpringMVC是在Servlet的基础上封装的,包含在SpringFramework内

  • 即我们本来想在Spring基础上使用web,但是得整合servlet,比较麻烦,SpringMVC是Spring的一个模块,里面封装了Servlet

1.3.1、SpringMVC的几个特点

  • SpringMVC框架跟其他的WebMVC框架一样,都是请求驱动。

  • SpringMVC框架设计并围绕一个能够分发请求到控制器的核心Servlet(DispatcherServlet,即前端控制器)

  • SpringMVC框架和spring的IOC容器完全整合,并且允许使用spring中其他的所有功能。(这一条很重要)

  • SpringMVC框架设计的一个核心的原则就是"开闭原则"(OCP原则),对扩展开放,对修改关闭。所以SpringMVC框架中很多方法都是final的,不允许用户随意覆盖,但是却提供给用户很多可扩展的机制。

  • SpringMVC框架目前已经成为非常流行的web应用的框架

1.3.2、获取SpringMVC

  • 由于SpringMVC是Spring框架中的一个模块,所以我们下载Spring框架即可,因为里面包含了Spring框架的各个模块的相关东西,当然也包含了SpringMVC的(jar包、API文档、说明文档、源代码等)

  • SpringMVC框架的jar包分为四部分:

    1. web及webmvc的jar包(必选)

      • spring-web-x.x.x.RELEASE

      • spring-webmvc-x.x.x.RELEASE

    2. Spring框架的核心模块的jar包(必选)

      • spring-beans-x.x.x.RELEASE

      • spring-core-x.x.x.RELEASE

      • spring-context-x.x.x.RELEASE

      • spring-expression-x.x.x.RELEASE

      • spring-aop-x.x.x.RELEASE

    3. 日志的jar包(可选)

      • commons-logging-x.x

      • log4j-x.x.x

    4. 其他(可选)

      • jstl-1.2.jar (如果使用jsp的话,必须引入)

1.3.3、SpringMVC的核心组件

  • DispatcherServlet:前端控制器。用来过滤客户端发送过来,想要进行逻辑处理的请求

  • Controller/Headler:控制器/处理器。开发人员自定义,用来处理用户请求的,并且处理完成之后返回给用户指定视图的对象,相当于我们之前编写的Servlet

  • HandlerMapping:处理器映射器(简称映射器)。DispatcherServlet接收到客户端请求的URL之后,根据一定的匹配规则,再把请求转发给对应的Handler,这个匹配规则由 HandlerMapping 决定

  • HandlerAdaptor:处理器适配器(简称适配器)。用来适配每一个要执行的Handler对象。通过HandlerAdapter可以支持任意的类作为处理器。作用是告诉SpringMVC框架,将来需要调用Controller中的哪一个方法。

  • ViewResolver:视图解析器(简称解析器)。Controller/Headler返回的是逻辑视图名,需要有一个解析器能够将逻辑视图名转换成实际的物理视图。例如,Controller中返回的逻辑视图名字为"hello",解析器可以给这个逻辑视图名转换为真正的物理视图名,例如加入前缀和后缀:/WEB-INF/jsp/hello.jsp

  • SpringMVC的可扩展性,决定了视图可以有很多种,所以不同的情况下需要不同的视图解析器,例如:使用jsp充当视图的时候,就需要使用专门的jsp解析器

1.3.4、请求处理流程

当一个请求进来之前,SpringMVC中的核心组件会一起配合来完成本次请求的处理:

  1. 请求被前端控制器(DispatcherServlet)接收到

  2. 前端控制器(DispatcherServlet)根据映射器(HandlerMapping)中配置的映射关系,将这个请求转交给真正能够处理客户端请求的处理器(Controller/Headler)

  3. 处理器(Controller/Headler)通过适配器(HandlerAdaptor),执行指定方法进行处理,完成后返回给用户ModelAndView(模型和视图的结合体)

  4. 视图解析器(ViewResolver)根据ModelAndView中的逻辑视图名找到真正的物理视图

  5. 使用ModelAndView中的模型对视图进行渲染

1.4、三层架构与MVC的区别

  • MVC:

    • M 即Model(模型层),主要负责处理业务逻辑以及数据库的交互

    • V 即View(视图层),主要负责显示数据和提交数据

    • C 即Controller(控制层),主要是永作辅助捕获请求并控制请求转发

  • 三层架构:UI界面层、BLL业务逻辑层、DAL数据访问层

  • 三层是基于业务逻辑来分的,而mvc是基于页面来分的

  • MVC模式是一种复合设计模式,一种解决方案

  • 三层架构是种软件架构,通过接口实现编程

  • 三层架构模式是体系结构模式,MVC是设计模式

2、项目搭建

2.1、步骤

2.1.1、搭建步骤

  1. 构建Web项目

  2. 导入所需jar包

  3. 配置前端控制器 DispatcherServlet

  4. 编写 Controller 控制器(也称为 Handler 处理器)

  5. 配置处理器映射器(可省去,有默认配置)

  6. 配置处理器适配器(可省去,有默认配置)

  7. 配置视图解析器(可省去,有默认配置,但是前缀和后缀都为"")

  8. 配置控制器/处理器

2.1.2、访问流程

  1. 首先用户发送请求,前端控制器DispatcherServlet收到请求后自己不进行处理,而是委托给其他的Controller进行处理,前端控制器作为统一访问点,进行全局的流程控制

  2. DispatcherServlet把请求转交给HandlerMapping,HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器对象、多个HandlerInterceptor拦截器)对象

  3. DispatcherServlet再把请求转交给HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器。简单点说就是让我们知道接下来应该调用Handler处理器里面的什么方法

  4. HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名)

  5. ModelAndView的逻辑视图名交给ViewResolver解析器,ViewResolver解析器把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术

  6. View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术

  7. 最后返回到DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束

2.2、开始搭建

  • Spring配置文件中的处理器适配器,处理器映射器,视图解析器等都的配置都可以省略,都有默认配置。

  • 默认配置在spring-webmvc-x.x.x.RELEASE.jar中的 org.springframework.web.servlet 包下的DispatcherServlet.properties 文件中

  • 如果视图解析器没有配置的话,那么默认的前缀和后缀都是空("")

2.2.1、构建项目

2.2.2、引入jar

2.2.3、配置前端控制器DispatcherServlet

  • SpringMVC的前端控制器就是一个Servlet对象,继承自HttpServlet,所以需要在web.xml文件中配置

  • 框架会自动去当前应用的WEB-INF目录下查找名字为SpringMVC-servlet.xml文件(默认前缀和<servlet-name> 标签中的值一致)

  • SpringMVC是Spring提供的一个模块,Spring所有的模块都是基于Spring IOC功能的。

  • 所以SpringMVC的DispatcherServlet对象在初始化之前也会去实例化Spring的容器对象(ApplicationContext),那么就需要读取Spring的配置文件。

  • 默认SpringMVC会在web应用的WEB-INF目录下查找一个名字为[servlet-name]-servlet.xml文件,并且创建在这个文件中定义的bean对象。

  • 如果你提供的spring配置文件的名字或者位置和默认的不同,那么需要在配置servlet时同时指定配置文件的位置。也可以放在WEB-INF目录下

  • url-pattern:可写成 / ,拦截所有请求,

  • 但是这种情况,也会导致静态文件(jpg,js,css)被拦截后不能正常显示

  • 记住是 / ,而不是 /,如果使用/,那么请求时可以通过DispatcherServlet转发到相应的Controller中,但是Controller返回的时候,如返回的jsp,那么还会再次被拦截,这样导致404错误,即访问不到jsp。

//web.xml
<servlet>
  <servlet-name>SpringMVC</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-web-mvc.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
​
<servlet-mapping>
  <servlet-name>SpringMVC</servlet-name>
  <!--这里将url设置为*.action代表拦截所有以.action结尾的请求,这些请求都是Servlet去处理-->
  <url-pattern>*.action</url-pattern>
</servlet-mapping>
​
​
​
//spring-web-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 做配置 -->
</beans>

2.2.4、编写处理器Handler/Controller

  • 用 ModelAndView 的对象使用 addObject("name","jack"); 可以在页面中 配合 EL表达式${} 获取到

  • 处理器(Handler),也可以叫控制器(Controller),是用来接收并且处理用户请求的

  • 它是MVC中的部分C,因为此处的控制器主要负责功能处理部分:

    • 收集、验证请求参数并封装到对象上

    • 将对象交给业务层,由业务对象处理并返回模型数据

    • 返回ModelAndView(Model部分是业务层返回的模型数据,View部分为逻辑视图名)

  • 前端控制器(DispatcherServlet)主要负责整体的控制流程的调度部分:

    • 负责将请求委托给控制器进行处理(Handler/Controller)

    • 根据控制器返回的逻辑视图名选择具体的视图进行渲染(并把模型数据传入)

  • 因此MVC中完整的C(包含控制逻辑+功能处理)由(DispatcherServlet + Controller)组成

  • org.springframework.web.servlet.mvc.Controller 接口

    • 只有一个抽象方法: ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception

    • 方法中接收两个参数,分别对应Servlet对象中的request,response对象。可以从request中获取客户端提交过来的请求参数。

    • 返回值ModelAndView,既包含要返回给客户端浏览器的逻辑视图又包含要对视图进行渲染的数据模型

public class HelloController implements Controller{
  @Override
  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {
    String name = request.getParameter("name");
    //ModelAndView对象中包括了要返回的逻辑视图,以及数据模型
    ModelAndView mv = new ModelAndView();
    //设置逻辑视图名称
    mv.setViewName("hello");
    //设置数据模型
    mv.addObject("name", name);
    return mv;
  }
}

2.2.5、配置映射器(可省去,有默认配置)

  • Spring容器需要根据映射器来将用户提交的请求url和后台Controller/Handler进行绑定,所以需要配置映射器

  • 该映射器表示将请求的URL和Bean名字映射,如URL为 "/hello",则Spring配置文件必须有一个名字为"/hello"的Bean

  • 在spring-web-mvc.xml中配置

<!-- 配置映射器 -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

2.2.6、配置适配器(可省去,有默认配置)

  • 适配器可配置多个,不影响

  • 想要正确运行自定义处理器,需要配置处理器适配器,适配器的作用就是适配并调用处理器中的方法

  • 查看 SimpleControllerHandlerAdapter 源代码,就可指定它的作用了

  • 在spring-web-mvc.xml中配置

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

2.2.7、配置视图解析器(可省去,有默认配置,但是前缀和后缀都为"")

  • 当处理器执行完成后,返回给spring容器一个ModelAndView对象,这个对象需要能够被解析成与之相对应的视图,并且使用返回的Model数据对视图进行渲染

  • InternalResourceViewResolver:用于支持Servlet、JSP视图解析

  • viewClass:JstlView表示JSP模板页面需要使用JSTL标签库,classpath中必须包含jstl的相关jar包

  • prefix和suffix:视图页面的前缀和后缀(前缀+逻辑视图名+后缀)

  • 例如,传进来的逻辑视图名为hello,则该该jsp视图页面应该存放在"WEB-INF/jsp/hello.jsp"

  • 在spring-web-mvc.xml中配置

<!-- 配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <!-- 设置生成的视图的类型,如果是jsp的话,必须要引入jstl-1.2.jar -->
  <property name="viewClass"
  value="org.springframework.web.servlet.view.JstlView"/>
  
  <!-- 设置视图前缀 -->
  <property name="prefix" value="/WEB-INF/jsp/"/>
  
  <!-- 设置视图后缀 -->
  <property name="suffix" value=".jsp"/>
</bean>

2.2.8、配置处理器(必须)

  • 处理器以后肯定是配置多个的

  • 前面我们已经编写好了处理器(HelloController),想让正常工作,还需要将其配置到Spring容器中

  • 在spring-web-mvc.xml中配置

<!-- 配置处理器 -->
<bean name="/hello.action" class="com.briup.web.controller.HelloController"/>

2.2.9、配置日志文件log4j.properties

log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%-5p] %c - %m%n

2.2.10、在WEB-INF/jsp中新建hello.jsp文件、并将将项目的ContextPath设为/

  • 最好修改成 / ,可以方便很多

2.3、DispatcherServlet的映射路径

2.3.1、拦截所有请求(用的多些)

<url-pattern>/</url-pattern>
  • 这种情况,也会导致静态文件(jpg,js,css)被拦截后不能正常显示

  • 记住是 / ,而不是 /,如果使用/,那么请求时可以通过DispatcherServlet转发到相应的Controller中,但是Controller返回的时候,如返回的jsp,那么 /* 会导致再次被拦截,这样导致404错误,即访问不到jsp。

  • /的话就不会拦截访问jsp页面,但是 URI 引用不到样式

2.3.2、自定义拦截请求的后缀名

<url-pattern>*.action</url-pattern>
  • 这是最传统的方式,最简单也最实用。不会导致静态文件(jpg,js,css)被拦截。 但是Controller的后缀只能固定成某一种

2.3.3、解决使用 / 全部拦截时静态资源也被拦截的情况

  • 如果DispatcherServlet拦截 *.do 这样的有后缀的URL,就不存在访问不到静态资源的问题。

  • 如果DispatcherServlet拦截 / ,拦截了所有的请求,那么同时对静态文件的访问也就被拦截了。比如下面这些都会被拦截

<link href="static/css/hello.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="static/js/hello.js"></script>
<img alt="none" src="static/images/logo.png" />

2.3.3.1、利用tomcat的defaultServlet处理静态文件(解决方法1)

  • 配置在 web.xml 中

  • 即在tomcat内部,有默认的servlet,我们在web.xml配置映射,表示如果是这样的 请求进来,那么交给默认的servlet来处理。直接用就可以

  • 但是要配置多个,每种静态资源配置一个映射,

  • 并且要写在DispatcherServlet的前面(和tomcat版本有关),让defaultServlet先拦截请求。即配置在 web.xml中,最靠近web-app

  • 高性能

<servlet-mapping>
  <servlet-name>default</servlet-name>
  <url-pattern>*.jpg</url-pattern>
</servlet-mapping>
​
<servlet-mapping>
  <servlet-name>default</servlet-name>
  <url-pattern>*.js</url-pattern>
</servlet-mapping>
​
<servlet-mapping>
  <servlet-name>default</servlet-name>
  <url-pattern>*.css</url-pattern>
</servlet-mapping>

2.3.3.2、利用mvc:resources标签,不拦截静态文件(解决方法2)

  • 配置在 spring-web-mvc.xml 中。记得引入名称空间

  • 用的多些,因为配置明确清晰,方便阅读

  • 这次会被springmvc拦截,但是因为有路径映射配置,则可以正常访问

  • location:是静态资源的位置

  • mapping:映射地址,** 表示映射指定路径下所有的URL,包括子路径

  • 这种方式可以让 指定路径下的静态资源 不会被拦截

  • xml中头部声明中,mvc命名空间和schame文件的引入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 配置映射器 -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
​
<!-- 配置适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
​
<!-- 配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <!-- 设置生成的视图的类型 -->
  <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
  
  <!-- 设置视图前缀 -->
  <property name="prefix" value="/WEB-INF/jsp/"/>
  <!-- 设置视图后缀 -->
  <property name="suffix" value=".jsp"/>
</bean>
​
<!-- 配置处理器 -->
<bean name="/hello.action" class="com.briup.web.controller.HelloController"/>
​
<mvc:resources mapping="/static/images/**" location="/static/images/"/>
<mvc:resources mapping="/static/js/**" location="/static/js/"/>
<mvc:resources mapping="/static/css/**" location="/static/css/"/>
</beans>

2.3.3.3、利用mvc:default-servlet-handler/标签(解决方法3)

  • 配置在 spring-web-mvc.xml 中。

  • 这次会被springmvc拦截,但mvc会把静态资源的访问交给tomcat中默认的servlet来处理

  • 这种方式是最简单的配置方法,SpringMVC也是让tomcat中默认的servlet对静态资源进行处理

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 配置映射器 -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
​
<!-- 配置适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
​
<!-- 配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <!-- 设置生成的视图的类型 -->
  <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
  
  <!-- 设置视图前缀 -->
  <property name="prefix" value="/WEB-INF/jsp/"/>
  <!-- 设置视图后缀 -->
  <property name="suffix" value=".jsp"/>
</bean>
​
<!-- 配置处理器 -->
<bean name="/hello.action" class="com.briup.web.controller.HelloController"/>
​
<mvc:default-servlet-handler/>
</beans>

2.4、编码

  • 配置在 web.xml 中

  • SpringMVC中,提供了一个编码过滤器org.springframework.web.filter.CharacterEncodingFilter ,配置后,可以设置请求中的编码,之前我们都是自己手动写一个编码过滤器,然后进行配置。现在直接使用SpringMVC提供的编码过滤器,也能完成同样的功能

  • encoding、forceRequestEncoding、forceResponseEncoding,都是类的属性,我们在设置参数的值

<filter>
  <filter-name>CharacterEncodingFilter</filter-name>
  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  
  <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
  </init-param>
  
  <init-param>
    <param-name>forceRequestEncoding</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param>
    <param-name>forceResponseEncoding</param-name>
    <param-value>true</param-value>
  </init-param>
</filter>
​
<filter-mapping>
  <filter-name>CharacterEncodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

2.5、其他

  • 如果想直接在处理器/控制器里使用response向客户端写回数据,可以通过返回 null ,来告诉DispatcherServlet我们已经写出响应了,不需要它进行视图解析

  • 以前是返回 ModelAndView 对象,让里面的数据内容填充到视图里面,即渲染,

  • 现在我们想跳转视图,或者自己处理请求,不需要使用到视图解析器,就需要返回null

public class HelloController implements Controller{
@Override
  public ModelAndView handleRequest(HttpServletRequest request, 
      HttpServletResponse response) throws Exception {
    response.getWriter().write("Hello World!!");
    return null;
  }
}

# 3、适配器Adapter

  • 一般情况下,SimpleControllerHandlerAdapter 会是我们常用的适配器,也是SpringMVC中默认的适配器

  • 它首先会判断我们的handler是否实现了Controller接口,如果实现了,那么会调用Controller接口中的handleRequest方法。

  • 那么根据这种方式能看出,我们也可以有自己的适配器的实现,那么就可以让任意类成为SpringMVC中的handler了,无论我们的类是否实现了Controller接口

  • 主动建一个 adapter 包,需要自定义处理器接口、自定义适配器

  • 自定义适配器需要实现implements HandlerAdapter,重写里面的方法

  • 写自定义的Controller接口,和自定义的Adapter实现类,然后写一个Controller,让它实现我们自己的Controller,并对请求进行处理,最后在spring-web-mvc.xml中配置 适配器Adapter和处理器controller

  • 使用实现接口的方式,用instanceof容易判断一些,如果用注解的方式,还要去用反射获取注解,麻烦一些

//自定义处理器接口
public interface MyHandler{
  ModelAndView myHandler(HttpServletRequest request, HttpServletResponse response)throws Exception;
}
​
//自定义适配器
public class MyHandlerAdapter implements HandlerAdapter{
  @Override
  public boolean supports(Object handler) {
    return (handler instanceof MyHandler);
  }
  @Override
  public ModelAndView handle(HttpServletRequest request,
      HttpServletResponse response, Object handler)throws Exception {
    return ((MyHandler)handler).myHandler(request, response);
  }
  @Override
  public long getLastModified(HttpServletRequest request, Object handler) {
    return -1L;
  }
}
​
//编写Controller处理器
public class MyController implements MyHandler{
  @Override
  public ModelAndView myHandler(HttpServletRequest request,
      HttpServletResponse response) throws Exception {
    String name = request.getParameter("name");
    ModelAndView mv = new ModelAndView("hello");
    mv.addObject("name", name);
    return mv;
  }
}
​
//配置Adapter、Controller
<!-- 配置适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<bean class="com.briup.web.adapter.MyHandlerAdapter"/>
​
<!-- 配置处理器 -->
<bean name="/hello" class="com.briup.web.controller.HelloController"/>
<bean name="/test" class="com.briup.web.controller.MyController"/>

# 4、拦截器Interceptor

  • SpringMVC的处理器拦截器类似于Servlet 开发中的过滤器Filter,用于对处理器进行预处理和后处理

  • Interceptor的应用场景:日志记录、权限检查、性能监控、通用行为

  • 使用拦截器,可以在处理器(Handler/Controller)中的方法执行之前或之后,添加额外代码功能,这个效果就是使用代理对象完成的AOP功能

  • 有时候,我们可能只需要实现三个回调方法中的某一个,但是如果实现 HandlerInterceptor 接口的话,三个方法必须实现。

  • Spring 提供了一个 HandlerInterceptorAdapter 适配器,允许我们只实现需要的回调方法。在 HandlerInterceptorAdapter 中,对 HandlerInterceptor 接口中的三个方法都进行了空实现,其中preHandle方法的返回值,默认是true

4.1、拦截器接口:HandlerInterceptor

  • 拦截器接口:org.springframework.web.servlet.HandlerInterceptor

  • preHandle方法:预处理回调方法,实现处理器的预处理,第三个参数为的处理器(本次请求要访问的那个Controller)返回值

    • true表示继续流程,即将请求放行(如调有下一个拦截器或处理器)

    • false表示流程中断,此时需要通过response来产生响应

  • postHandle方法:后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理,modelAndView也可能为null

  • afterCompletion方法:整个请求处理完毕回调方法,即在视图渲染完毕时回调

public interface HandlerInterceptor {
  default boolean preHandle(HttpServletRequest request, 
      HttpServletResponse response, Object handler) throws Exception {
    return true;
  }
  
  default void postHandle(HttpServletRequest request, 
      HttpServletResponse response, Object handler,
      @Nullable ModelAndView modelAndView) throws Exception {
  }
  
  default void afterCompletion(HttpServletRequest request, 
      HttpServletResponse response, Object handler,
      @Nullable Exception ex) throws Exception {
  
  }
}

4.2、创建拦截器并配置

4.2.1、自定义拦截器类

  • 自主创建interceptor包,自定义拦截器,让其实现HandlerInterceptor或继承HandlerInterceptorAdapter

public class MyInterceptor extends HandlerInterceptorAdapter{
  @Override
  public boolean preHandle(HttpServletRequest request, 
      HttpServletResponse response, Object handler) throws Exception {
    System.out.println("MyInterceptor preHandle");
    return true;
  }
  @Override
  public void postHandle(HttpServletRequest request, 
      HttpServletResponse response, Object handler,
      ModelAndView modelAndView) throws Exception {
    System.out.println("MyInterceptor postHandle");
  }
  @Override
  public void afterCompletion(HttpServletRequest request, 
      HttpServletResponse response, Object handler, Exception ex) throws Exception {
    System.out.println("MyInterceptor afterCompletion");
  }
}

4.2.2、配置拦截器

4.2.2.1、在映射器中配置拦截器

  • 配置在spring-web-mvc.xml 里面

  • 只要是走的BeanNameUrlHandlerMapping 这个映射器,就会使用到

<!-- 配置拦截器 -->
<bean name="myInterceptor" class="com.briup.web.interceptor.MyInterceptor"/>
​
<!-- 配置映射器 -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
  <!-- 注入拦截器,可以有多个,其实是调用AbstracthandlerMappeing中的
           setInterceptors方法,即property。方法的参数是可变参数 ... 这个 -->
  <!-- 将来使用该映射器的请求,都会被指定拦截器拦截 -->
  <property name="interceptors">
    <list>
      <ref bean="myInterceptor"/>
    </list>
  </property>
</bean>

4.2.2.2、专门的MVC标签mvc:interceptors配置拦截器(推荐)

  • SpringMVC中,还专门提供了一个标签mvc:interceptors,来进行拦截器的配置

  • 在spring-web-mvc.xml中

  • 这里配置的/**,表示通配,全部拦截,指定路径和子路径

  • ref的bean只能由一个,要想要两个连接器,再配一个mvc:interceptor

<!-- 配置拦截器 -->
<bean name="myInterceptor" class="com.briup.web.interceptor.MyInterceptor"/>
​
<!-- 使用专门的mvc标签配置拦截器 -->
<mvc:interceptors>
  <mvc:interceptor>
    <!-- 指定需要拦截的请求路径 -->
    <mvc:mapping path="/**"/>
    <!-- 指定不进行拦截的请求路径 -->
    <mvc:exclude-mapping path="/test"/>
    <!-- 指定拦截器对象 -->
    <ref bean="myInterceptor"/>
  </mvc:interceptor>
</mvc:interceptors>
​
<!-- 配置映射器 -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
​
<!-- 配置适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<bean class="com.briup.web.adapter.MyHandlerAdapter"/>
​
<!-- 配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
​
<!-- 设置生成的视图的类型 -->
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
  <!-- 设置视图前缀 -->
  <property name="prefix" value="/WEB-INF/jsp/"/>
  <!-- 设置视图后缀 -->
  <property name="suffix" value=".jsp"/>
</bean>
​
<!-- 配置处理器 -->
<bean name="/hello" class="com.briup.web.controller.HelloController"/>
<bean name="/test" class="com.briup.web.controller.MyController"/>
​
<!-- 静态资源交给tomcat的defaultServlet -->
<mvc:default-servlet-handler/>

4.3、拦截器是单例的

  • SpringMVC中的拦截器是单例,因此不管多少用户请求多少次,都只有一个拦截器实现,即非线程安全,因为不是每个线程一个拦截器,多线程共享资源就会不安全

  • 所以在必要时,可以在拦截器中使用ThreadLocal,它是和线程绑定的,一个线程一个ThreadLocal。

  • 例如,A 线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal。

  • 那么将值存到ThreadLocal中,那么一个线程在执行期间,就知道能拿到自己线程中的ThreadLocal里面的值了。这个ThreadLocal里面的值会一直存在,直至这个线程的工作结束

//拦截器是单例,不是线程安全的,所以这里使用ThreadLocal
//一般对 Interceptor 中的 全局属性  使用
private ThreadLocal<Long> local = new ThreadLocal<>();

4.4、Servlet的Filter和SpringMVC的Interceptor的区别

  • 二者都可用于对处理器进行预处理和后处理

  • Filter 是servlet中的组件,任何Java web工程都可以使用

  • Interceptor 是属于SpringMVC的,只有使用了SpringMVC框架的工程才能使用

  • Filter 可以拦截任意资源路径,也包括SpringMVC中的Controller

  • Interceptor 只能拦截SpringMVC中的Controller

# 5、注解配置(Servlet的配置多用注解方式)

  • 在实际的项目中,我们更多的会使用注解的方式,来对Controller进行配置,一般情况下,使用俩个注解 就可以完成Controller的配置:

    • @Controller

    • @RequestMapping

  • 使用新的配置文件,用以专门区分:spring-web-mvc-annotation.xml,记得在web.xml修改要读取的配置文件

  • 并且一个Controller类中,也可以写多个不同的路径处理方法,就相当于以前写一堆Servlet了

5.1、@Controller

  • 必须要配置spring-web-mvc-annotation.xml,从而开启SpringMVC注解功能,

  • 使用注解后,就不需要再实现那些特定的接口了,被扫描到的时候,告诉Spring的IOC容器它是一个Controller

  • 使用 @Controller 后,TestController就成为了SpringIOC容器中的bean对象了,但是需要我们在xml中,指定Spring扫描的包路径,这样Spring就可以找到TestController类上面的注解了。

  • 同时,需要开启springmvc注解功能的支持spring-web-mvc-annotation.xml文件配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 指定spring扫描的包路径 -->
<context:component-scan base-package="com.briup.web.controller"></context:component-scan>
​
<!-- 开启springmvc的注解功能 -->
<mvc:annotation-driven></mvc:annotation-driven>
​
<!-- 配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
  <property name="prefix" value="/WEB-INF/jsp/"/>
  <property name="suffix" value=".jsp"/>
</bean>
​
<!-- 配置静态资源不被拦截 -->
<mvc:default-servlet-handler/>
</beans>

5.2、@RequestMapping

  • @RequestMapping 注解,可以指定控制器中的方法和对应的映射路径

  • @RequestMapping({"/", "/test"}) 里面可以配置多个路径,多个地址连接到同一个映射中

  • @RequestMapping 不仅可以放在方法上面,也可以放在类上面

@Controller
public class TestController {
  @RequestMapping("/test")
  public ModelAndView test(){
    ModelAndView mv = new ModelAndView("hello");
    mv.addObject("name", "jack");
    return mv;
  }
}

5.3、使用注解后的便利之处

  • Controller中的方法,除了返回 ModelAndView 之外,还可以返回 String ,表示返回的逻辑视图名

  • Controller中的方法,返回值还可以是void,表示不需要前端控制器处理,方法内部就处理本次请求的响应了。即不需要跳转到页面里面了,我们自己

  • 方法需要什么,你直接在参数列表里面写,SpringMVC如果知道这个参数,会帮你自动注入,比如HttpServletResponse response

  • Controller中的方法,如果要接收参数,可以在参数列表中声明request,如果要传值给视图,可以在参数列表中声明model,将数据存放在model中即可,然后就会让前端控制器去使用 model 里面的数据来渲染页面,你只需要把数据放在 model里面,然后 return "" 到页面里面就可以了

  • 注意,request、response、model,这些参数只要声明,就可以直接使用,springmvc在调用的时候会自动注入的,参数的名字和顺序没要求,只要参数的类型正确即可

  • 注意,这里的参数model,表示模型,将数据存入后,可以传给视图,并取出显示

@Controller
public class TestController {
  @RequestMapping("/test")
    public ModelAndView test(){
    ModelAndView mv = new ModelAndView("hello");
    return mv;
  }
  
  @RequestMapping("/test1")
  public String test1(){
    return "hello";
  }
  
  @RequestMapping("/test2")
  public void test2(HttpServletResponse response) throws IOException{
    response.getWriter().println("hello world");
  }
  
  @RequestMapping("/test3")
  public String test3(HttpServletRequest request, Model model) throws IOException{
    String name = request.getParameter("name");
    model.addAttribute("name", name);
    return "hello";
  }
}

5.4、其他注解

Spring2.5中,引入注解对Controller进行支持

@Controller,用于标识是处理器类

@RequestMapping,请求到处理器功能方法的映射规则

@RequestParam,请求参数到处理器功能处理方法的方法参数上的绑定

@ModelAttribute,请求参数到Model中的绑定

@SessionAttributes,用于声明session级别的数据存储

@InitBinder,自定义数据绑定的注册支持

Spring3中,引入了更多的注解,其中包含了对 RESTful 架构风格的支持

@CookieValue,cookie数据到处理器功能处理方法的方法参数上的绑定

@RequestHeader,请求头数据到处理器功能处理方法的方法参数上的绑定

@RequestBody,请求的body体的绑定

@ResponseBody,处理器功能处理方法的返回值作为响应体

@ResponseStatus,定义处理器功能处理方法/异常处理器返回的状态码和原因

@ExceptionHandler,注解式声明异常处理器

@PathVariable,请求URI 中的模板变量部分到处理器功能处理方法的方法参数上的绑定,从而支持

RESTful 架构风格的URI

Spring4以后,引入了的一些注解,简化之前注解的配置

@RestController

@GetMapping

@PostMapping

@PutMapping

@DeleteMapping

@PatchMapping

# 6、请求映射

  • 传参方法:

    1. uri后面加 ? 传参,get

    2. 请求体中的传参,post

    3. 路径中传参,/user/{id}

6.1、Http协议请求

  • 从格式中我们可以看到【请求方法、URL、请求头信息、请求正文】这四部分一般是可变的

  • 因此我们可以把请求中的这些信息在Controller的【功能处理方法】中进行的映射

POST /login HTTP1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,en;q=0.8,zh;q=0.5,en-US;q=0.3
Connection: keep-alive
Cookie: JSESSIONID=DBC6367DEB1C024A836F3EA35FCFD5A2
Host: 127.0.0.1:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:82.0) Gecko/20100101
Firefox/82.0
username=tom&password=123
​
​
​
请求方法 URL 协议版本号
请求头信息
请求头信息
请求头信息
..
回车换行
请求正文

6.2、请求映射

  1. URL路径映射:使用URL映射到处理器的功能处理方法

  2. 请求方法映射:例如,限定功能处理方法只处理GET请求

  3. 请求参数映射:例如,限定只处理包含username参数的请求

  4. 请求头映射:例如,限定只处理"Accept=application/json"的请求

6.2.1、URL路径映射

6.2.1.1、普通URL路径映射

  • 注解中只出现一个参数且参数名为value的话,可以将参数名去掉

    • @RequestMapping(value="/test")

    • @RequestMapping("/hello")

  • 多个URL路径可以映射到同一个处理器的功能处理方法

    • @RequestMapping({"/test", "/user/hello"})

6.2.1.2、URI模板映射

  • {userId}占位符,请求的URL可以是"/users/123456"或"/users/abcd",之后可以通过@PathVariable可以提取URI模板模式中的{userId}中的值

  • 即在参数列表中这样使用 @PathVariable("userId") String userId,这个userId就是路径中拿到的 可变值userId

    • @RequestMapping(value="/users/{userId}")

  • 占位符可以使用多个,也可以交替使用

    • @RequestMapping(value="/users/{userId}/create")

    • @RequestMapping(value="/users/{userId}/topics/{topicId}")

6.2.1.3、Ant风格的URL路径映射

  • 使用匹配,代表该路径及其子路径,可以匹配"/users/abc/123"

  • 但"/users/123"将会被【URI模板模式映射中的"/users/{userId}"模式优先映射到】,因为精确的先被匹配

    • @RequestMapping(value="/users/**")

  • 使用 ?匹配,?代表有且只有一个字符,?可以有多个,代表固定个数的字符匹配,比如 /product/??? ,只能匹配三个字符的路径

  • 可匹配"/product/1"或"/product/a",但不匹配"/product"或"/product/aa"

    • @RequestMapping(value="/product/?")

  • *代表0~n个字符

  • 可匹配"/productabc"或"/product",但不匹配"/productabc/abc"

    • @RequestMapping(value="/product*")

  • Ant风格和URI模板变量风格可混用

  • 可匹配"/products/abc/abc/123"或"/products/123"

    • @RequestMapping(value="/products/**/{productId}")

6.2.1.4、正则表达式风格的URL路径映射

  • 格式为:{变量名 : 正则表达式},然后通过 @PathVariable 可以提取{XXX:正则表达式}中的XXX这个变量的值

  • \d表示数字,但是\在java的字符串中是特殊字符,所以得是 \d

  • @RequestMapping(value="/products/{categoryCode:\d+}-{pageNumber:\d+}")

    • 可以匹配"/products/123-1"

    • 但不能匹配"/products/abc-1"

  • @RequestMapping(value="/user/{userId:^\d{4}-[a-z]{2}$}")

    • 可以匹配"/user/1234-ab"

6.2.1.4.1、正则表达式的规则
  • 括号:

  1. [abc]:查找方括号之间的任何字符

  2. [^abc]:查找任何不在方括号之间的字符

  3. [0-9]:查找任何从 0 至 9 的数字

  4. [a-z]:查找任何从小写 a 到小写 z 的字符

  5. [A-Z]:查找任何从大写 A 到大写 Z 的字符

  6. [A-z]:查找任何从大写 A 到小写 z 的字符

  7. (red|blue|green):查找任何指定的选项

  • 元字符:(\记得转义 \,查找也相当于匹配)

  1. .:查找单个任意字符,除了换行和行结束符 .,如果要表示 . 这个字符,需要转义

  2. \w:查找单词字符。 字母、数字、_

  3. \W:查找非单词字符。非 字母、数字、_

  4. \d:查找数字

  5. \D:查找非数字字符

  6. \s:查找空白字符

  7. \S:查找非空白字符

  8. \b:匹配单词边界

  9. \B:匹配非单词边界

  10. \0:查找 NUL 字符

  11. \n:查找换行符

  12. \f:查找换页符

  13. \r:查找回车符

  14. \t:查找制表符

  15. \v:查找垂直制表符

  • 量词:

  1. n+:匹配任何包含至少一个 n 的字符串

  2. n*:匹配任何包含零个或多个 n 的字符串

  3. n?:匹配任何包含零个或一个 n 的字符串

  4. n{X}:匹配包含 X 个 n 的序列的字符串

  5. n{X,Y}:匹配包含 X 到 Y 个 n 的序列的字符串

  6. n{X,}:匹配包含至少 X 个 n 的序列的字符串

  7. n$:匹配任何结尾为 n 的字符串

  8. ^n:匹配任何开头为 n 的字符串

  9. ?=n:配任何其后紧接指定字符串 n 的字符串

  10. ?!n:匹配任何其后没有紧接指定字符串 n 的字符串

6.2.2、请求方法映射

  • 一般获取数据为GET请求方法,提交表单一般为POST请求方法。

  • 但之前URL路径映射方式对任意请求方法都是接受的,因此我们需要某种方式来告诉相应的功能处理方法,只处理如GET方式的请求或POST方式的请求

  • 除了GET、POST方式请求之外,还有PUT、DELETE,以及HEAD、OPTIONS、TRACE

  • @RequestMapping(value="/user/{userId:\d+}", method=RequestMethod.GET)

    • 可以匹配"/user/100"

    • 并且请求方式只能是GET

  • @RequestMapping(value="/hello", method={RequestMethod.POST,RequestMethod.GET})

    • 可以匹配"/hello"

    • 并且请求方式只能是POST或者GET

6.2.3、请求参数映射

6.2.3.1、请求数据中有指定参数名

  • @RequestMapping(params="create",method=RequestMethod.GET)

  • 必须是GET方式请求

  • 请求参数中必须有create参数

6.2.3.2、请求数据中没有指定参数名

  • @RequestMapping(params="!create", method=RequestMethod.GET)

  • 必须是GET方式请求

  • 请求参数中不能出现create参数

6.2.3.3、请求数据中指定参数名和值

  • @RequestMapping(params="username=tom")

  • 请求参数中必须出现username=tom

6.2.3.4、请求数据中指定参数名!=值

  • @RequestMapping(params="username!=tom")

  • username参数名可以不出现

  • 如果出现,那么参数值一定不能等于tom

6.2.3.5、组合使用是"且"的关系

  • @RequestMapping(params={"create","username=tom"})

  • 请求参数中必须有create参数

  • 请求参数中必须有username=tom

6.2.4、请求头映射

  • 请求头中的Content-Type属性,表示请求中携带给服务器的数据是什么类型的

  • 请求头中Accept属性,表示请求希望服务器返回的数据是什么类型的

  • Accept:/ 可以适配所有情况,且这个是默认值。所以不要随便设置固定值,例如:Accept:text/html 或 Accept:application/json

6.2.4.1、请求头数据中有指定参数名

  • @RequestMapping(value="/header/test1", headers="Accept")

  • 请求的URL必须为"/header/test1"

  • 请求头中必须有Accept参数

6.2.4.2、请求头数据中没有指定参数名

  • @RequestMapping(value="/header/test2", headers="!abc")

  • 请求的URL必须为"/header/test2"

  • 请求头中必须没有abc参数

6.2.4.3、请求头数据中指定参数名=值

  • @RequestMapping(value="/header/test3", headers="Content-Type=application/json")

  • 请求的URL必须为"/header/test3"

  • 请求头中必须有"Content-Type=application/json"参数

6.2.4.4、请求头数据中指定参数名!=值

  • @RequestMapping(value="/header/test4",headers="Accept!=text/html")

  • 请求的URL必须为"/header/test4"

  • 请求头中必须有Accept参数,但是值不等于text/html

6.2.4.5、组合使用是"且"的关系

  • @RequestMapping(value="/header/test5", headers={"Accept!=text/html", "abc=123"})

  • 请求的URL必须为"/header/test5"

  • 请求头中必须有"Accept"参数,但值不等于"text/html"

  • 请求中必须有参数"abc=123"

6.2.4.6、consumes属性和produces属性(重要)

  • consumes是规定 headers 请求的请求头中的Content-Type字段,即请求给服务器带的数据的类型

  • produces是规定 headers 请求的请求头中的Accept字段,即请求希望服务器返回的数据的类型,并且有默认值 /

  • consumes 指定处理请求中所提交数据类型(消费)

  • @RequestMapping(value="/test",consumes="application/json")

  • 方法仅处理请求中,Content-Type为"application/json"的情况

  • produces 指定返回(响应)的数据类型(生产)

  • @RequestMapping(value= "/test", produces="application/json")

  • 方法将产生json格式的数据

  • 此时根据请求头中的Accept进行匹配,如请求头"Accept=application/json"时即可匹配

# 7、方法参数

  • Controller中处理方法的参数,支持多种类型,当我们使用这些类型的变量作为参数,在Controller中处理方法的参数列表声明出来的时候,SpringMVC框架在调用方法的时候,会自动注入参数的

7.1、HttpServletRequest 和 HttpServletResponse

@RequestMapping("/test1")
public void test1(HttpServletRequest request,HttpServletResponse response){
  System.out.println("request = "+request);
  System.out.println("response = "+response);
}

7.2、InputStream/OutputStream 和 Reader/Writer

@RequestMapping("/test2")
public void test2(InputStream in,OutputStream out){
  //对应request.getInputStream();
  System.out.println("in = "+in);
  //对应response.getOutputStream();
  System.out.println("out = "+out);
}
​
@RequestMapping("test2")
public void test2(Reader in,Writer out){
  //request.getReader();
  System.out.println("in = "+in);
  //response.getWriter();
  System.out.println("out = "+out);
}

7.3、WebRequest/NativeWebRequest

  • WebRequest是SpringMVC提供的统一请求访问接口,可以访问各个范围的数据;

  • NativeWebRequest继承了WebRequest,并提供访问本地ServletAPI的方法

@RequestMapping("/test3")
public String test3(WebRequest webRequest, NativeWebRequest nativeWebRequest){
  System.out.println(webRequest.getParameter("test"));
  webRequest.setAttribute("name", "tom",WebRequest.SCOPE_REQUEST);
  System.out.println(webRequest.getAttribute("name",WebRequest.SCOPE_REQUEST));
  
  HttpServletRequest request =
      nativeWebRequest.getNativeRequest(HttpServletRequest.class);
  HttpServletResponse response =
      nativeWebRequest.getNativeResponse(HttpServletResponse.class);
  return "test";
}

7.4、HttpSession

@RequestMapping("/test4")
public void test4(HttpSession session){
  System.out.println("session = "+session);
}

7.5、值对象(Value Object)

  • 请求中含有 username=tom&age=20,这些值就会被封装到User对象中,需要参数名和属性名字一致

  • 这里没用@Autowired,说明这个对象不需要注入到容器中

@RequestMapping("/test5")
public void test5(User user){
  System.out.println("user = "+user);
}

7.6、Model、Map、ModelMap

  • SpringMVC提供Model、Map或ModelMap让我们能去封装模型数据,将来再传入视图中

  • 虽然此处注入的是三个不同的类型参数,但三者是同一个对象

  • 往三个里面的任意一个存值,都放到里MVC中的M中,视图解析后,可以使用M中,即模型中的数据进行渲染

@RequestMapping("/test6")
public String test6(Model m1, Map<String,Object> m2, ModelMap m3){
  System.out.println(m1 == m2);
  System.out.println(m2 == m3);
  System.out.println(m1.getClass());
  System.out.println(m2.getClass());
  System.out.println(m3.getClass());
  
  m1.addAttribute("a", "a");
  m2.put("b", "b");
  m3.put("c", "c");
  return "hello";
}

7.7、HttpEntity<T>和ResponseEntity<T>

  • HttpEntity可以获取请求中各个部分的内容

  • ResponseEntity作为返回类型,可以自定义响应的各个部分,即将自定义的响应然后返回

@RequestMapping("/test7")
public String test7(HttpEntity<String> httpEntity){
  //获得请求中的所有的header部分
  HttpHeaders headers = httpEntity.getHeaders();
  //获得请求中的所有的body部分
  String body = httpEntity.getBody();
  
  //请求头中的key可能会有多个value,所以用List
  Set<Entry<String,List<String>>> set = headers.entrySet();
  StringBuffer s = new StringBuffer();
  
  for(Entry<String,List<String>> entry:set){
    String key = entry.getKey();
    s.append(key+": ");
    List<String> list = entry.getValue();
    for(String value:list){
      s.append(value+" ");
    }
    System.out.println(s.toString());
    s.setLength(0);
  }
  System.out.println("----------------");
  System.out.println("body = "+body);
  return "hello";
}
​
​
​
@RequestMapping("/test7")
public ResponseEntity<String> test7(){
  //创建响应头对象
  HttpHeaders headers = new HttpHeaders();
  
  //创建MediaType对象
  MediaType mt = new MediaType("text","html",Charset.forName("UTF-8"));
  //设置ContentType
  headers.setContentType(mt);
  
  //准备好响应体
  String content = new String("hello world");
  
  //根据 响应内容/响应头信息/响应状态码 创建响应对象
  ResponseEntity<String> re = 
      new ResponseEntity<String>(content,headers,HttpStatus.OK);
      
  //返回ResponseEntity对象
  return re;
}

7.8、其他

  • 除了以上这些参数类型之外,SpringMVC还支持的参数类型有:

    • SessionStatus

    • RedirectAttributes

    • BindingResult

    • Errors

    • Locale

    • Principal

    • UriComponentsBuilder

# 8、数据绑定

  • 请求中携带的参数,或者将数据存放到Model(request)或者 session中,或者设置响应中携带的数据,这些事情Springmvc中都提供了相应的注解,可以帮我们很方便的完成

  • 基本上所有注解的 name和value属性 都是一个意思

8.1、@RequestParam

  • @RequestParam 用于将请求参数映射到功能处理方法的参数上

  • 但不会把这个参数自动放在模型里面

  • 注意:名字一致就可以自动接收注入,不需要这个注解,但是最好写上

  • 例如,获取请求中的name参数的值,并注入到test1方法参数username中,但是如果没name参数,则会抛异常

  • value和name是一样的;

  • required用来指定是否一定需要此参数,用于判断不存在时是否会抛出异常,默认是true;

  • defaultValue可指定默认值

    • defaultValue默认值可以是spring中的SpEL 表达式,如 defaultValue = "#{systemProperties['java.vm.name']}"

    • systemProperties是SpringIOC容器中的一个Properties对象,该对象中很多系统变量,要取值只需#{systemProperties['key']}即可

@RequestMapping("/test1")
public String test1(@RequestParam("name") String username){
  System.out.println("username = "+username);
  return "hello";
}
​
@RequestMapping("/test1")
public String test1(@RequestParam(value="name",required=false,
    defaultValue="briup") String username){
  System.out.println("username = "+username);
  return "hello";
}

8.2、@PathVariable

  • 获取的是路径中的值,必须加上注解,名字一致也没用,当然如果名字一致,那么@PathVariable中可以不写指定名字的参数

  • @PathVariable 用于将请求URL中的模板变量映射到功能处理方法的参数上

  • @PathVariable还可以自动的把数据放到模型中去,页面中可以直接使用EL表达式取值:${name}

@RequestMapping("/test2/{name}")
public String test2(@PathVariable("name") String username){
  System.out.println("username = "+username);
  return "hello";
}

8.3、@CookieValue

  • @CookieValue 用于将请求的Cookie数据映射到功能处理方法的参数上

  • 例如,自动将JSESSIONID 值入参到sessionId 参数上

@RequestMapping("/test3")
public String test3(@CookieValue(value="JSESSIONID",required=false)
    String sessionId){
  System.out.println("sessionId = "+sessionId);
  return "hello";
}
​
@RequestMapping("/test3")
public String test3(@CookieValue(value="JSESSIONID",required=false) 
    Cookie cookie){
  System.out.println("cookie = "+cookie);
  return "hello";
}

8.4、@RequestHeader

  • @RequestHeader用于将请求的头中的值映射到功能处理方法的参数上

  • 例如,自动将请求头"User-Agent"的值,入参到userAgent参数上,并将"Accept"请求头值入参到accepts参数上

@RequestMapping("/test4")
public String test4(@RequestHeader("User-Agent") String userAgent,
    @RequestHeader(value="Accept") String[] accepts){
  System.out.println("userAgent = "+userAgent);
  System.out.println("accepts = "+Arrays.toString(accepts));
  return "hello";
}

8.5、@ModelAttribute

  • @ModelAttribute 注解用于将方法的参数或方法的返回值绑定到指定的模型属性上,并返回给Web视图

  • 并且如果你没加这个注解,但是参数是一个 引用对象,那么SpringMVC也会默认帮你把这个 引用对象放到Model中

  • 但如果处理器方法的参数是基本类型或者字符串,是不会默认存到模型中的。不会默认存,但你可以主动存进去

  • 当把引用对象放入模型的时候,除了把User对象放入模型中,还把参数的验证结果存放了进来,之外在数据验证的时候会用到

  • 该注解可以用在方法上,也可以用在参数上

  • 加了该注解的方法,执行优先级很高,当你访问这个Controller时,在任何映射方法执行之前,就会执行该方法,并将方法的参数和返回值放入 模型,然后在页面中使用EL表达式就能获取到

  • key默认为返回类型的首字母小写,但可以指定key

  • 如果模型中已经有了指定key的值, @ModelAttribute还会从模型中取值

    • @ModelAttribute("name")会先从模型中查询key为name的值

    • 如果有key为name的值,那么就取出来注入到这个方法参数name上,这个值显示为:tom

    • 此时访问地址并传参 /test2?name=lucy,这个lucy的是无法接收的

    • 除非再添加一个注解 @RequestParam("name"),这时候才可以拿到请求参数中的name值:lucy

@RequestMapping("/model")
@Controller
public class ModelAttributeController {
  //任何映射方法执行之前,都会先调用该方法,将返回值存入模型中
  //key默认为返回类型的首字母小写,string
  @ModelAttribute
  public String addModel1() {
    return "hello";
  }
  
  //任何映射方法执行之前,都会先调用该方法,将返回值存入模型中
  //如果是集合,key默认为泛型类型首字小写+List,这里为stringList
  @ModelAttribute
  public List<String> addModel2() {
    return java.util.Arrays.asList("hello","world");
  }
  
  //任何映射方法执行之前,都会先调用该方法,将返回值存入模型中
  //可以指定key的值,这里设置为name
  @ModelAttribute("name")
  public String addModel3() {
    return "tom";
  }
​
  @RequestMapping("/test1")
  public String test(Model model) {
    //输出model,可以看出里面存放的key和value
    System.out.println(model);
    return "hello";
  }
​
  @RequestMapping("/test2")
  public String test(@ModelAttribute("name") String name,Model model) {
  System.out.println(model);
    return "hello";
  }
}

8.6、@SessionAttributes

  • @SessionAttributes 注解只用作用在类上,作用是将指定的 Model 的键值对保存在 session 中

  • 先访问test1,然后就先会把name=mary放在Model中,然后因为注解的原因,会把Model中的name这个k-v放入session,然后再访问test2,就可以通过Model或session来获取值

  • 如果同时使用@ModelAttribute 和 @SessionAttributes,并且@ModelAttribute用在参数上,此时会报错,即@SessionAttributes({"name"})、@ModelAttribute("name") String name

@SessionAttributes({"name"})
@RequestMapping("/session")
@Controller
public class SessionAttributesController {
  @RequestMapping("/test1")
  public String test1(Model model) {
    model.addAttribute("name", "mary");
    return "hello";
  }
  @RequestMapping("/test2")
  public String test2(HttpSession session) {
    System.out.println(session.getAttribute("name"));
    return "hello";
  }
}

8.7、@Value

  • @Value("#{}") 能将一个SpEL表达式结果映射到功能处理方法的参数上

  • 比如:@Value("#{systemProperties['java.vm.version']}") String jvmVersion,

    • systemProperties是SpringIOC容器中的一个Properties对象

    • 该对象中很多系统变量,要取值只需#{systemProperties['key']}即可

  • 也可以获取Spring容器中的bean对象

  • @Value("#{}"):从Spring容器中获取资源

  • @Value("${}"):从项目文件或外部文件中获取资源

@RequestMapping("/value")
@Controller
public class ValueController {
  @RequestMapping("/test1")
  public String test1(
      @Value("#{systemProperties['java.vm.version']}") String jvmVersion) {
    System.out.println(jvmVersion);
    return "hello";
  }
  @RequestMapping("/test2")
  public String test2(@Value("#{user.name}") String username) {
    System.out.println(username);
    return "hello";
  }
​
  @RequestMapping("/test3")
  public String test3(@Value("#{user.sayHello()}") String msg) {
    System.out.println(msg);
    return "hello";
  }
  
  @RequestMapping("/test4")
  public String test4(@Value("#{user.sayHello('mary')}") String msg) {
    System.out.println(msg);
    return "hello";
  }
  
  //这里的?号,是为了防止空指针,以为sayHello()方法可能返回null,不为null的时候才会继续调用
  @RequestMapping("/test5")
  public String test5(@Value("#{user.sayHello()?.toUpperCase()}") String msg) {
    System.out.println(msg);
    return "hello";
  }
}
  • 如果要调用的某个类是外部类的静态方法,而不是spring中定义的bean,使用表达式T()

@RequestMapping("/value")
@Controller
public class ValueController {
  @RequestMapping("/test6")
  public String test6(@Value("#{T(java.lang.Math).random()}") String msg) {
    System.out.println(msg);
    return "hello";
  }
}

8.8、@InitBinder

  • @InitBinder 注解可以解决类型转换的问题,传过来一个字符串,想要把这个字符串转换成执行的日期格式,就可以将参数自动转换类型,不需要主动调用

  • 默认情况下,SpringMVC无法将1999-11-11这样的字符串转为日期,添加上面format方法并使用注解@InitBinder后,就可以正常转换了

  • 还有另一种方式,是将@DataTimeFormat 配置在 实体类中的

@RequestMapping("/binder")
@Controller
public class InitBinderController {
  @RequestMapping("/test")
  public String test(User user) {
    System.out.println(user);
    return "hello";
  }
  
  @InitBinder
  public void format(WebDataBinder binder){
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    //指定日期转换的格式,true表示允许为空
    binder.registerCustomEditor(Date.class, 
        new CustomDateEditor(dateFormat, true));
  }
}
​
public class User {
  private long id;
  private String name;
  @DateTimeFormat(pattern = "yyyy-MM-dd")
  private Date dob;
}

8.9、@Scope

  • 区分是否为单例,在构造器中加一句话,多次new对象,看会有几次构造器输出

  • Controller默认情况下和servlet一样也是单例,但是spring提供了一个@Scope注解可以让Controller对象变为非单例,只需在Controller类上面加入@Scope("prototype")即可

  • singleton、prototype、session、request、globalsession

@Controller
@RequestMapping("/hello")
@Scope("prototype")
public class HelloWorldController{
  //....
}

8.10、@RequestBody(读)

  • @RequestBody 表示从请求正文中取值,即请求必须是POST,因为如果是GET,那么这个值就在路径里面了,这个注解就取不到

  • 该注解一般用于接收请求体中的JSON格式字符串,并转换为java对象,但是需要引入操作json的相关jar包:jackson-core-2.11.3.jar、jackson-annotations-2.11.3.jar、jackson-databind-2.11.3.jar

@RequestMapping("/request-body")
@Controller
public class RequestBodyController {
  @RequestMapping(value="/test1",method = RequestMethod.POST)
  public String test(@RequestBody String content) {
    System.out.println(content);
    return "hello";
  }
  
  @RequestMapping(value="/test2",method = RequestMethod.POST,
        consumes ="application/json")
  public String test(@RequestBody User user) {
    System.out.println(user);
    return "hello";
  }
}
  • 也可以使用ajax,发出POST请求,并携带json格式字符串:ajax.jsp

  • http中的Content-Type,在jquery中是contentType

  • 在Controller中return "ajax" 来中转,因为在WEB-INF下,但可以在spring-web-mvc.xml 中配置 mvc:view-controller 这个标签,就可以不需要在Controller中使用方法来中转了,会自动被加上前后缀

<mvc:view-controller path="/前缀/ajax1" view-name="myajax"/>
  • 但是这样不符合 ajax ,因为ajax是异步交互,局部刷新,而我们这里是返回一个页面

  • 可能会想到我们应该让Controller来 return null,然后再参数列表中引入Writer out,使用out写回,而不是返回一整个页面,但是有更好的方法, @ResponseBody注解

$(function(){
 $("#btn").on("click",function(){
    var jsonObj = {
      id:1,
      name:"tom",
      dob:"1999-11-11"
    };
    var jsonStr = JSON.stringify(jsonObj);  
  
    $.ajax({
      type: "post",
      url: "/request-body/test",
      contentType: "application/json",
      data:  jsonStr,
      dataType: "text",
      success: function(data){
        console.log("data = "+data);
      }
    });
  }); 
});
  • http协议请求中的字段,jquery的ajax代码,RequestMapping注解中的属性,三者之间的对应关系

8.11、@ResponseBody(配合ajax,写)

  • @ResponseBody 注解用于将处理器中功能处理方法返回的对象,经过转换为指定格式后,写入到Response对象的body数据区,也就是响应正文

  • @ResponseBody注解,表示这个方法的返回值,将存入到响应正文中

  • @ResponseBody 一般在处理器方法返回的数据不是html页面,而是json数据的时候使用,并且可以自动将对象转为json格式的字符串,放入响应体中,并返回。前提是得有jar包

  • JSON.stringify(jsonObj)、JSON.parse(jsonStr):json对象变字符串是为了方便传输,json字符串变对象是为了方便取值

@RequestMapping("/response-body")
@Controller
public class ResponseBodyController {
  //test方法和test1方法效果一样,是等价的
  @RequestMapping("/test1")
  public void tes1t(PrintWriter out) {
    out.println("hello world");
  }
  
  @ResponseBody
  @RequestMapping("/test2")
  public String test() {
    return "hello world";
  }
  
  @ResponseBody
  @RequestMapping("/test3")
  public User test1() {
    User user = new User();
    user.setId(1L);
    user.setName("tom");
    user.setDob(new Date());
    return user;
  }
}

8.11.1、处理日期转JSON

  • 之前的@InitBinder 和 @DataTimeFormat ,是处理传参时,将字符串参数变成日期格式

  • 现在是要将 日期格式 变成json字符串,即序列化。否则json字符串拿到的是 时间戳

  • 需要自定义一个json的日期类型格式化类,然后在实体类的property即getter上加 @JsonSerialize(using=DateJsonSerializer.class)

    • @DateTimeFormat(pattern = "yyyy-MM-dd") ,是SpringMVC接收客户端请求中参数的时候,字符串转日期对象的格式

    • @JsonSerialize(using=DateJsonSerializer.class) ,是Controller处理方法的返回值,将java对象转为json格式字符串的日期格式

//java对象中的日期属性 转 json字符串的时候,日期格式的设置
public class DateJsonSerializer extends JsonSerializer<Date> {
  @Override
  public void serialize(Date value, JsonGenerator jgen, 
      SerializerProvider provider) throws IOException,JsonProcessingException {
    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
    String formattedDate = formatter.format(value);
    jgen.writeString(formattedDate);
  }
}
​
public class User {
  private long id;
  private String name;
  @DateTimeFormat(pattern = "yyyy-MM-dd")
  private Date dob;
  
  @JsonSerialize(using=DateJsonSerializer.class)
  public Date getDob() {
    return dob;
  }
  //其他的getter 和 setter
}

# 9、数据交互

  • 在项目中,经常会遇到这样的场景,前端页面中发送ajax请求给Controller中的处理方法,处理方法返回 json格式的字符串给这个ajax请求。当然,前面页面发送的请求中携带的参数,也可以是正常的k=v的形式

  • var arr = [];是数组 ; {} 是对象或Map,因为都是k-v键值对

9.1、Controller接收参数

  • 注意,参数名字要和客户端传的参数名一致,否则需要使用 @RequestParam 来指定参数名

9.1.1、参数是基本数据类型/包装类型/String

@RequestMapping("test")
public String test(int age){...}
public String test(long id){...}
public String test(boolean flag){...}
public String test(Long id){...}
public String test(String name){...}

9.1.2、参数是数组类型

  • 注意,客户端传值类似于这样:name=tom&name=lili

@RequestMapping("test")
public String test(int[] age){...}
public String test(long[] id){...}
public String test(boolean[] flag){...}
public String test(Long[] id){...}
public String test(String[] name){...}

9.1.3、参数是数组类型(json格式,用@ResponseBody)

//后端Controller,加@RequestBody后,收发数据的格式都是 json 了
@RequestMapping("/index")
@ResponseBody
public String index(@RequestBody String[] arr){...}
​
​
​
//前端的js代码,[]代表数组,{}代表对象或Map
$("#btn").on("click",function(){
  var arr = [];
  arr.push("hello");
  arr.push("world");
  $.ajax({
    type:"post",
    url:"test/index",
    contentType:"application/json",
    data:JSON.stringify(arr),
    dataType:"json",
    success:function(data){
      console.log("data = "+data);
    }
  });
});

9.1.4、参数是类类型

  • 注意,客户端传值类似于这样:id=1&name=tom&dob=1999-11-11

  • 前端如果是使用POST发给后端,并且后端有@ResponseBody,那么必须加上contentType这个字段

@RequestMapping("test")
public String test(User user){...}

9.1.5、参数是类类型的数组(json格式)

@RequestMapping("/test")
public String test(@RequestBody User[] users){...}
​
//前端的js代码
$("#btn").on("click",function(){
  var arr = [];
  var json1 = {id:1,name:"tom",dob:"1999-11-11"};
  var json2 = {id:2,name:"zss",dob:"2000-11-11"};
  arr.push(json1);
  arr.push(json2);
  
  $.ajax({
    type:"post",
    url:"/test",
    contentType:"application/json",
    data:JSON.stringify(arr),
    dataType:"json",
    success:function(data){
      console.log("data = "+data);
    }
  });
});

9.1.6、参数是List/Set集合(json格式)

@RequestMapping("/index")
@ResponseBody
public String index(@RequestBody List<User> list){...}
​
//前端的js代码
$("#btn").on("click",function(){
  var arr = [];
  var json1 = {id:1,name:"tom",dob:"1999-11-11"};
  var json2 = {id:2,name:"zss",dob:"2000-11-11"};
  arr.push(json1);
  arr.push(json2);
  
  $.ajax({
    type:"post",
    url:"test/index",
    contentType:"application/json",
    data:JSON.stringify(arr),
    dataType:"json",
    success:function(data){
      console.log("data = "+data);
    }
  });
});

9.1.7、参数是Map集合(json格式)

@RequestMapping("/index")
@ResponseBody
public String index(@RequestBody Map<Long,User> map){...}
​
//前端的js代码
$("#btn").on("click",function(){
  var map = {};
  var json1 = {id:1,name:"tom",dob:"1999-11-11"};
  var json2 = {id:2,name:"zss",dob:"2000-11-11"};
  map[json1.id] = json1;
  map[json2.id] = json2;
  $.ajax({
    type:"post",
    url:"test/index",
    contentType:"application/json",
    data:JSON.stringify(map),
    dataType:"json",
    success:function(data){
      console.log("data = "+data);
    }
  });
});

9.1.8、其他情况

  • 在遇到特殊情况下,可以使用自定义类型转换器,将一个类型转换为另一种类型

  • 当需要将String转换成Data时,转换器会自动启动

//实现这个接口
public interface Converter<S, T> {
  T convert(S source);
}
​
//转换器 负责把String转换为Date
public class StringToDateConverter implements Converter<String,Date>{
  private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
  @Override
  public Date convert(String str) {
    Date date = null;
    try {
      if(str!=null && !"".equals(str.trim())){
        date = dateFormat.parse(str);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    return date;
  }
}
​
​
​
//配置文件
<mvc:annotation-driven conversion-service="formatService"/>
​
<bean name="formatService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
  <property name="converters">
    <set>
      <!-- 自己编写的类型转换器,可以有多个 -->
      <bean class="com.briup.web.converter.StringToDateConverter"></bean>
    </set>
  </property>
</bean>

9.2、Controller返回数据

9.2.1、给ajax请求返回单值

@ResponseBody
@RequestMapping(value="test1",consumes="application/json",
    produces="application/json" ,method=RequestMethod.POST)
public String test1(@RequestBody User user){
  System.out.println("user = "+user);
  return "hello world";
}

9.2.2、给ajax请求返回数组(json格式)

@ResponseBody
@RequestMapping(value="test2",consumes="application/json",
    produces="application/json" ,method=RequestMethod.POST)
public String[] test2(@RequestBody User user){
  System.out.println("user = "+user);
  return new String[]{"hello","world","tom"};
}

9.2.3、给ajax请求返回自定义类型的数组(json格式)

@ResponseBody
@RequestMapping(value="test3",consumes="application/json",
    produces="application/json" ,method=RequestMethod.POST)
public User[] test3(@RequestBody User user){
  System.out.println("user = "+user);
  return new User[]{new User(1L,"tom1"),new User(2L,"tom2"),new User(3L,"tom3")};
}

9.2.4、给ajax请求返回List集合(json格式)

@ResponseBody
@RequestMapping(value="test4",consumes="application/json",
    produces="application/json" ,method=RequestMethod.POST)
public List<User> test4(@RequestBody User user){
  System.out.println("user = "+user);
  return Arrays.asList(new User(1L,"tom1"),new User(2L,"tom2"),new User(3L,"tom3"));
}

9.2.5、给ajax请求返回Set集合(json格式)

@ResponseBody
@RequestMapping(value="test5",consumes="application/json",
    produces="application/json" ,method=RequestMethod.POST)
public Set<User> test5(@RequestBody User user){
  System.out.println("user = "+user);
  Set<User> set = new HashSet<>();
  set.add(new User(1L,"tom1"));
  set.add(new User(2L,"tom2"));
  set.add(new User(3L,"tom3"));
  return set;
}

9.2.5、给ajax请求返回Map集合(json格式)

@ResponseBody
@RequestMapping(value="test6",consumes="application/json",
    produces="application/json" ,method=RequestMethod.POST)
public Map<String,User> test6(@RequestBody User user){
  System.out.println("user = "+user);
  Map<String,User> map = new HashMap<>();
  map.put("tom1", new User(1L,"tom1"));
  map.put("tom2", new User(2L,"tom2"));
  map.put("tom3", new User(3L,"tom3"));
  return map;
}

# 10、页面跳转

  • 需要记住,重定向的话,用户无法访问到WEB-INF中的内容,但可以使用 mvc:view-controller 进行配置,这个就可以访问到了

  • 如果你让 功能处理方法 的返回类型是 void,那么SpringMVC就会把 /Controller地址/访问地址 这个地址,作为视图名去访问,所以void也会返回视图,视图名就是访问地址名

10.1、Servlet跳转和重定向方式

  • 因为在Controller中的功能处理方法上可以获得到request和response,所以可以像之前servlet中一样,进行服务器内部跳转和客户端重定向

  • 记得return null

@RequestMapping("/forward-redirect")
@Controller
public class ForwardAndRedirectController {
  @RequestMapping("/a")
  public String testA(HttpServletRequest request,
      HttpServletResponse response) throws ServletException, IOException{
    System.out.println("testA");
    
    //服务器内部跳转到一个页面
    //request.getRequestDispatcher("/WEB-INF/jsp/hello.jsp")
        .forward(request,response);
        
    //服务器内部跳转到一个功能处理方法
    //request.getRequestDispatcher("/forward-redirect/test")
        .forward(request,response);
        
    //客户端重定向到另一个功能处理方法
    response.sendRedirect("/forward-redirect/test");
    return null;
  }
}

10.2、字符串表示跳转和重定向

  • Controller中可以使用字符串表示服务器内部跳转和客户端重定向

  • return "字符串":这个字符串是一个 视图名,我们跳转到这个页面中,这个字符串不能跳转到 另一个 处理方法中,因为这个只能被当作视图

  • 但是可以用forward: 或 redirect: 来告诉SpringMVC,我不是要跳到页面中,而是跳转或重定向到指定 功能处理方法

@RequestMapping("/forward-redirect")
@Controller
public class ForwardAndRedirectController {
  @RequestMapping("/b")
  public String testB(){
    System.out.println("testB");
    //服务器内部跳转到另一个功能处理方法
    //return "forward:/forward-redirect/test";
    
    //客户端重定向到另一个功能处理方法
    //return "redirect:/forward-redirect/test";
    
    //服务器内部跳转到一个页面
    return "hello";
  }
}

10.3、ModelAndView

  • Controller中使用ModelAndView进行跳转和重定向

@RequestMapping("/forward-redirect")
@Controller
public class ForwardAndRedirectController {
  @RequestMapping("/c")
  public ModelAndView testC() throws ServletException, IOException{
    System.out.println("testC");
    
    //服务器内部跳转到另一个功能处理方法
    //ModelAndView mv = new ModelAndView("forward:/forward-redirect/test");
    
    //客户端重定向到另一个功能处理方法
    //ModelAndView mv = new ModelAndView("redirect:/forward-redirect/test");
    
    //服务器内部跳转到一个页面
    ModelAndView mv = new ModelAndView("hello");
    
    return mv;
  }
}

# 11、数据验证

  • 通常在项目中使用较多的是前端校验,比如页面中js校验。对于安全要求较高的建议在服务端同时校验

  • SpringMVC使用hibernate的实现的校验框架validation,所以需要导入相关依赖的jar包

  • SpringMVC、Hibernate、javax:

    • javax提供对象中属性值的验证规范,是注解形式

    • SpringMVC 使用了 Hibernate实现了注解的验证,也不止Hibernate这一种,有很多种,我们使用了Hibernate框架的验证实现

11.1、测试使用

11.1.1、实体类

  • @DateTimeFormat(pattern = "yyyy-MM-dd")是为了接收参数时候的日期字符串格式的设置

public class Teacher {
  @NotNull(message = "ID不能为空")
  private Long id;
  
  @Size(min=5,max=8,message = "长度需要在5-8之间")
  private String name;
  
  @Min(value = 18,message = "年龄必须是18以上")
  private Integer age;
  
  @Past(message = "必须是过去的日期时间")
  @DateTimeFormat(pattern = "yyyy-MM-dd")
  private Date dob;
  //getter、setter
}

11.1.2、Controller

  • 方法参数teacher前面加了注解@Valid,表示要做数据验证

  • 注意,方法参数bindingResult表示验证的结果如果有错误信息,会存入该对象中

@RequestMapping("/valid")
@Controller
public class ValidController {
  @RequestMapping(value="/test",method={RequestMethod.GET,RequestMethod.POST})
  public String addTeacher(@Valid Teacher teacher,BindingResult bindingResult){
    //如果验证数据中有错误信息,将保存在bindingResult对象中
    if(bindingResult.hasErrors()){
      List<ObjectError> errorList = bindingResult.getAllErrors();
      for(ObjectError error : errorList){
        System.out.println(error.getDefaultMessage());
      }
    }
    return "hello";
  }
}

http://127.0.0.1:8989/valid/test?name=tom&age=10&dob=2999-11-11

11.2、配合页面完成数据验证

  • 数据校验之后,如果有错误信息,那么可以使用spring提供的标签库中的标签在页面中显示校验信息

  • 若不写action地址,点提交,那么就会默认以POST的方式给当前页面

11.2.1、JSP页面

  • 引入SpringFramework的标签库,即Spring封装好的表单

  • 表单中有 modelAttribute="teacher",绑定跳转过来的模型中的对象,给这个对象赋值

  • sf:errors代表如果有错误,就在这里显示,根据modelAttribute绑定的

//valid.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="sf" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>valid.jsp</title>
</head>
<body>
  <sf:form method="post" modelAttribute="teacher">
    <sf:label path="name">用户名:</sf:label><sf:input path="name"/>
    <sf:errors path="name" cssStyle="color:red"></sf:errors><br>
    
    <sf:label path="age"> 年 龄:</sf:label><sf:input path="age"/>
    <sf:errors path="age" cssStyle="color:red"></sf:errors><br>
    
    <sf:label path="dob"> 生 日:</sf:label><sf:input path="dob"/>
    <sf:errors path="dob" cssStyle="color:red"></sf:errors><br>
    
    <input type="submit" value="提交"/>
  </sf:form>
</body>
</html>

11.2.2、Controller页面

  • 用户访问页面之前,先让模型中有一个teacher实体类对象,因为form表单指定了modelAttribute

  • 先验证teacher,然后把错误信息存在BindingResult对象中,然后如果你又回到了form表单页面,sf:errors 会自动绑定错误信息

@RequestMapping("/valid")
@Controller
public class ValidController {
  //支持GET请求,模型中添加teacher对象,并返回valid视图页面
  @RequestMapping(value="/test", method = {RequestMethod.GET})
  public String showAddPage(Model model){
    //保证返回到valid页面的时候,模型中有teacher对象
    if(!model.containsAttribute("teacher")){
      model.addAttribute("teacher", new Teacher());
    }
    return "valid";
  }
  
  //支持POST请求,接收表单数据,做数据验证
  //有错误信息,就返回valid视图,显示错误信息
  //没有错误信息,就返回hello视图
  @RequestMapping(value="/test",method = {RequestMethod.POST})
  public String addTeacher(@Valid Teacher teacher,BindingResult bindingResult){
    //如果验证数据中有错误信息,将保存在bindingResult对象中
    if(bindingResult.hasErrors()){
      List<ObjectError> errorList = bindingResult.getAllErrors();
      for(ObjectError error : errorList){
        System.out.println(error.getDefaultMessage());
      }
      return "valid";
    }
    return "hello";
  }
}

11.3、常用验证注解

  • 常用的验证注解有:javax.validation.constraints.*

注解作用
@Null值只能为null
@NotNull值不能为null
@NotEmpty值不为null且不为空
@NotBlank值不为null且不为空(先去除首尾空格)
@Pattern正则表达式验证
@Size限制长度在x和y之间
@Max最大值
@Min最小值
@Future必须是一个将来的日期(和现在比)
@Past必须是一个过去的日期(和现在比)
@Email校验email格式

# 12、异常处理

  • 当Controller中的处理方法中,如果在运行期间抛出了异常,这时候就需要我们处理这个异常。

  • 记住:不要自己try-catch,因为必须得从Controller抛出去

  • 在SpringMVC中可以把异常统一进行处理,可以xml配置,也可以注解配置

12.1、XML方式

12.1.1、默认的异常解析器

  • 只要在xml配置文件中,添加一个异常解析器即可,并且指定一般异常需要跳转的视图,以及特定的异常需要跳转的视图

  • 这样就可以在抛出异常的时候跳转到

12.1.1.1、在XML中添加配置

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
  <!-- 定义默认的异常处理页面,都是property -->
  <!-- value="error/error" 表示error/目录下的逻辑视图,名字为error -->
  <property name="defaultErrorView" value="error/error"></property>
  
  <!-- 即异常对象,定义异常处理页面用来获取异常信息的变量名,默认名为exception -->
  <property name="exceptionAttribute" value="ex"></property>
  
  <!-- 定义需要特殊处理的异常,用简单类名或全限定名作为key,异常页名的逻辑视图名作为value,key写不写全都可以 -->
  <property name="exceptionMappings">
    <props>
      <prop key="IOException">error/error_io</prop>
      <prop key="java.sql.SQLException">error/error_sql</prop>
    </props>
  </property>
</bean>

12.1.1.2、指定的错误页面

  • 在jsp目录下建立error目录,放入显示错误的页面

  • error里面放error_io 或 error_sql 各种页面,可在配置中进行指定配置

  • 可以使用jsp的脚本显示信息,也可以用 EL表达式显示错误信息

//jsp脚本
<!-- 因为spring中修改了异常的默认名字,所以这里是ex -->
<% Exception ex = (Exception)request.getAttribute("ex"); %>
<H2>Exception: <%= ex.getMessage()%></H2>
<P/>
<% ex.printStackTrace(new java.io.PrintWriter(out)); %>
​
​
​
//EL表达式
<div>${ex }</div>
<div>${ex.message }</div>

12.1.1.3、Controller的功能处理方法

@RequestMapping("/error")
@Controller
public class ErrorController {
  @RequestMapping("/test")
  public void test() throws IOException {
      Random random = new Random();
      int num = random.nextInt(10);
      if(num%2==0) {
      throw new RuntimeException("Runtime异常测试");
    }else {
      throw new IOException("IO异常测试");
    }
  }
}

12.1.2、自定义的异常解析器

  • 实现接口 HandlerExceptionResolver ,实现类中指定不同的异常跳转不同的错误页面

  • 自己建立一个resolver包,在包下建立不同的自定义解析器

12.1.2.1、自定义的Resolver

public class MyExceptionResolver implements HandlerExceptionResolver{
  @Override
  public ModelAndView resolveException(HttpServletRequest request,
      HttpServletResponse response, Object handler, Exception ex) {
    String viewName = "error/error";
    
    if(ex instanceof IOException) {
      viewName = "error/error_io";
    }else if(ex instanceof SQLException) {
      viewName = "error/error_sql";
    }
    
    ModelAndView mv = new ModelAndView(viewName);
    mv.addObject("ex", ex);
    return mv;
  }
}
​
//返回的另一种方式
Map<String,Object> model = new HashMap<>();
model.put("ex", ex);
return new ModelAndView(viewName, model);

12.1.2.2、在XML中添加配置

  • 即最后一个<bean>,自定义的异常解析器

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 指定spring扫描的包路径 -->
<context:component-scan base-package="com.briup.web.annotation"></context:component-scan>
​
<!-- 开启springmvc的注解功能 -->
<mvc:annotation-driven></mvc:annotation-driven>
​
<!-- 配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
  <property name="prefix" value="/WEB-INF/jsp/"/>
  <property name="suffix" value=".jsp"/>
</bean>
​
<!-- 配置静态资源不被拦截 -->
<mvc:default-servlet-handler/>
​
<!-- 配置自定义异常解析器 -->
<bean class="com.briup.web.resolver.MyExceptionResolver"></bean>
</beans>

12.2、注解方式

12.2.1、@ExceptionHandler

  • 有一个前提,这个有ExceptionHandler注解的方法,必须与出异常的功能处理方法 在同一个Controller中,即处理异常的方法 和 抛出异常的方法 必须 在同一个controller中,所以需要下面的另一个注解

  • 使用@ExceptionHandler注解作用在方法上面,参数是具体的异常类型。一旦系统抛出这种类型的异常时,会引导到该方法来处理

@RequestMapping("/error")
@Controller
public class ErrorController {
  @RequestMapping("/test")
  public void test() throws IOException {
    Random random = new Random();
    int num = random.nextInt(10);
    if(num%2==0) {
      throw new RuntimeException("Runtime异常测试");
    }else {
      throw new IOException("IO异常测试");
    }
  }
​
  //指定需要的处理的异常类型,现在是所有的异常都能处理,也可以写具体了,来专门捕获
  @ExceptionHandler({Exception.class})
  public String ex(Exception ex,Model model) {
    model.addAttribute("ex", ex);
    
    String viewName = "error/error";
    if(ex instanceof IOException) {
      viewName = "error/error_io";
    }else if(ex instanceof SQLException) {
      viewName = "error/error_sql";
    }
    
    return viewName;
  }
}

12.2.2、@ControllerAdvice + @ExceptionHandler

  • 使用@ControllerAdvice 和@ExceptionHandler 可以全局控制异常,异常处理方法 和 抛出异常方法可以分开

  • 这样就可以把异常处理方法放到单独的地方了,和Controller分开

  • 但是不仅要写这个Handler,还要修改XML配置,让Spring知道它是一个ControllerAdvice,即指定Spring扫描的包路径

  • 我们这里配置的两个包路径,一个是扫描Controller的,另一个是扫描Resolver的

@ControllerAdvice
public class GlobalExceptionHandler {
  @ExceptionHandler({Exception.class})
  public String ex(Exception ex,Model model) {
    model.addAttribute("ex", ex);
    String viewName = "error/error";
    if(ex instanceof IOException) {
      viewName = "error/error_io";
    }else if(ex instanceof SQLException) {
      viewName = "error/error_sql";
    }
    return viewName;
  }
}
​
​
​
<!-- 指定spring扫描的包路径 -->
<context:component-scan base-package=
      "com.briup.web.annotation,com.briup.web.resolver">
</context:component-scan>

13、文件上传和文件下载

13.1、普通文件上传

  • 引入两个jar包:commons-fileupload-1.2.2.jar、commons-io-2.0.1.jar

13.1.1、用户进行上传操作的页面

  • enctype的属性值,一定要设置为 "multipart/form-data ,

  • 可以多个 相同name的input 这里可以同时上传俩个文件,因为在Controller中是接收的数组

//upload_test.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>upload.jsp</title>
</head><body>
  <h1>普通上传</h1>
  <form action="/upload/test" method="post" 
        enctype="multipart/form-data">
    <input type="file" name="file"><br>
    <input type="file" name="file"><br>
    <input type="submit" value="上传">
  </form>
</body></html>

13.1.2、Controller

@RequestMapping("/upload")
@Controller
public class UploadController {
  private static final String UPLOAD_BASE_PATH = "F:/MyFile";
​
  @RequestMapping(value="/test", method=RequestMethod.POST)
  public String test(@RequestParam("file") MultipartFile[] files,
      HttpServletRequest request) {
    if (files != null && files.length > 0) {
      for (MultipartFile file : files) {
        // 保存文件
        saveFile(request, file);
      }
    }
    // 重定向到/upload,在xml文件中可以配置/upload路径对应 upload_test 逻辑视图名
    return "redirect:/upload";
  }
  
  private void saveFile(HttpServletRequest request, MultipartFile file) {
    // 判断文件是否为空
    if (!file.isEmpty()) {
      try {
        //保存的文件路径
        //加上原始名字,需要的话,可以给文件名上加时间戳
        String filePath = UPLOAD_BASE_PATH + file.getOriginalFilename();
        File newFile = new File(filePath);
        
        //文件所在目录不存在就创建
        if (!newFile.getParentFile().exists()){
          newFile.getParentFile().mkdirs();
        }
        
        // 核心操作,转存文件
        file.transferTo(newFile);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}

13.1.3、配置XML

<!-- SpringMVC上传文件时,需要配置MultipartResolver处理器 -->
<!-- 注意:bean的名字不要改,一定要叫multipartResolver,否则上传报错 -->
<bean name="multipartResolver" 
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
  <property name="defaultEncoding" value="UTF-8"/>
  <!-- 指定所上传文件的总大小不能超过指定字节大小 -->
  <property name="maxUploadSize" value="20000000"/>
</bean>
​
<!-- 配置请求中如果访问/upload路径,那么就直接返回 upload_test 逻辑视图名 -->
<mvc:view-controller path="/upload" view-name="upload_test"/>

13.2、ajax文件上传

  • 在页面中,引入ajax上传的插件,也可以使用ajax完成上传功能

13.2.1、用户进行上传操作的页面,XML配置和普通的一样

  • 需要在用户进行上传操作的页面中,引入jQuery和ajax上传的插件

  • 先把插件放到项目里面,再用script进行引入。jQuery必须在ajax上面

<script type="text/javascript" src="/static/js/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="/static/js/ajaxfileupload.js"></script>
  • input:button:找input中type为button的对象

<script type="text/javascript" src="/static/js/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="/static/js/ajaxfileupload.js"></script>
<script type="text/javascript">
  $(function(){
    $("input:button").on("click",function(){
      if(!$("#myfile").val()){
        alert("请选择上传文件");
        return;
      }
      ajaxFileUpload();
    });
  });
    
  function ajaxFileUpload(){
    $.ajaxFileUpload({
      url: "/upload/ajax",
      fileElementId: "myfile",
      dataType: "text",
      success: function (data){
        $("#myfile").val("");
        //或者这样 $("input:file").val("");
        $("#sp").html(data).css("color","green");
      },
      error: function (data){
        $("#sp").html(data).css("color","red");
      }
    });
  }
</script>
</head><body>
  <h1>ajax上传</h1>
  <!-- ajax上传不需要表单 -->
  <!-- 这个上传框的id值一定要写,需要和js中的设置对应 -->
  <input id="myfile" type="file" name="myfile"><br>
  <input type="button" value="上传"><br>
  <span id="sp"></span>
</body>

13.2.2、Controller

  • saveFile方法没变

@RequestMapping(value="/ajax",method=RequestMethod.POST)
@ResponseBody
public String ajax(@RequestParam(value="myfile",required=false)
    MultipartFile[] files, HttpServletRequest request) throws Exception {
  if (files != null && files.length > 0) {
    for (MultipartFile file : files) {
      saveFile(request, file);
    }
  }
  return "上传成功";
}

13.2.3、解决乱码

  • 以前是用流写的,然后我们会设置request、response的charset,但是我们现在用到是SpringMVC的,我们没设置

  • SpringMVC默认处理的字符集是ISO-8859-1,使用 @ResponseBody 注解后,springmvc会用HttpMessageConverter 消息转换机制。

  • HttpMessageConverter 接口的作用,就是可以将一个指定类型的数据,转换为HTTP协议的消息。

  • 例如HttpMessageConverter会这样做:

    • 将一个字符串,封装为HTTP协议的响应(StringHttpMessageConverter),默认字符编码是ISO-8859-1,所以会是:Content-Type: text/html;charset=ISO-8859-1

    • 将一个对象转换为json字符串,再封装为HTTP协议的响应(MappingJackson2HttpMessageConverter)

13.2.3.1、XML文件中配置StringHttpMessageConverter

<!-- 开启springmvc的注解功能 -->
<mvc:annotation-driven>
  <!-- 配置消息转换器,指定返回的字符编码 -->
  <mvc:message-converters>
    //配置StringHttpMessageConverter这个转换器
    <bean class="org.springframework.http.converter.StringHttpMessageConverter">
      <property name="supportedMediaTypes">
        <list>
          <value>text/plain;charset=utf-8</value>
          <value>text/html;charset=utf-8</value>
        </list>
      </property>
    </bean>
  </mvc:message-converters>
</mvc:annotation-driven>

13.3、文件下载

  • SpringMVC的下载功能,只需要自己设置response信息中的各个部分就可以,可以使用之前学习过的ResponseEntity<T> 来完成

13.3.1、jsp页面

///WEB-INF/jsp/download_test.jsp
<body><a href="/download/test?fileName=测试.txt">点击下载</a></body>

13.3.2、xml中配置jsp的跳转路径

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

13.3.3、Controller

  • 因为下载的文件名字,在响应头中,所以这里要专门设置一下编码,以免中文乱码

@RequestMapping("/download")
@Controller
public class DownloadController {
  private static final String UPLOAD_BASE_PATH = "F:/MyFile";
​
  @RequestMapping("/test")
  public ResponseEntity<byte[]> test(String fileName,
        HttpServletRequest request) throws IOException {
    //创建要下载的文件对象
    File file = new File(UPLOAD_BASE_PATH,fileName);
    //org.apache.commons.io.FileUtils
    byte[] buf = FileUtils.readFileToByteArray(file);
    
    //处理一下要下载的文件名字,解决中文乱码。故意写成ISO格式,浏览器解析的时候就是中文了
    //即如果中文传过去,会中文乱码,你现在中文变ISO,ISO传过去就不乱码,ISO格式一显示就正常了
    String downFileName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
    
    //创建响应头信息的对象
    HttpHeaders headers = new HttpHeaders();
    //设置下载的响应头信息,通知浏览器,响应正文的内容是要下载的,不用浏览器解析
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    //设置弹框时,设置下载的文件显示的名字
    headers.setContentDispositionFormData("attachment", downFileName);
    
    //通过响应内容、响应头信息、响应状态来构建一个响应对象并返回
    ResponseEntity<byte[]> re = new ResponseEntity<byte[]>
        (buf, headers, HttpStatus.CREATED);
    return re;
  }
}

# 14、SSM整合

  • 可以参考之前servlet+spring+myabits的整合部分,这里只是把servlet换成了springmvc

  • 因为SpringMVC就是Spring的一部分,天然就是结合在一起的,所以SSM不需要整合SpringMVC,只需要整合MyBatis即可

14.1、建表

drop table teacher;
create table teacher(
  id number primary key,
  name varchar2(22),
  age number,
  salary number
);

14.2、新建项目,导入jar,导入jQuery

14.3、实体类

package com.briup.bean;
public class Teacher {
private Integer id;
private String name;
private Integer age;
private Double salary;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Teacher [id=" + id + ", name=" + name + ", age=" + age + ",
salary=" + salary + "]";
}
}

14.4、dao层接口

package com.briup.dao;
import com.briup.bean.Teacher;
public interface ITeacherDao {
public void insertTeacher(Teacher t);
public Teacher selectTeacherById(int id);
}

14.5、mybaits映射文件,TeacherMapper.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.briup.dao.ITeacherDao">
<insert id="insertTeacher" parameterType="Teacher">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select my_seq.nextVal from dual
</selectKey>
insert into teacher(id,name,age,salary)
values(#{id},#{name},#{age},#{salary})
</insert>
<select id="selectTeacherById" parameterType="int" resultType="Teacher">
select id,name,age,salary
from teacher
where id = #{id}
</select>
</mapper>

14.6、service接口和实现类

package com.briup.service;
import com.briup.bean.Teacher;
public interface ITeacherService {
void save(Teacher t);
}
​
​
​
package com.briup.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.briup.bean.Teacher;
import com.briup.dao.ITeacherDao;
import com.briup.service.ITeacherService;
@Service
public class TeacherServiceImpl implements ITeacherService{
@Autowired
private ITeacherDao teacherDao;
@Override
public void save(Teacher t) {
teacherDao.insertTeacher(t);
// throw new RuntimeException("异常测试");
}
}

14.7、Controller

package com.briup.web.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.briup.bean.Teacher;
import com.briup.service.ITeacherService;
@RequestMapping("/teacher")
@Controller
public class TeacherController {
@Autowired
private ITeacherService teacherService;
@ResponseBody
@RequestMapping("/test")
public String test(@RequestBody Teacher t) {
String msg = "添加成功!";
try {
teacherService.save(t);
} catch (Exception e) {
e.printStackTrace();
msg = "添加失败!";
}
return msg;
}
}

14.8、日志配置文件,log4j.properties

log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%-5p] %c - %m%n
#show sql
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.org.apache=INFO
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
# set springframework log-level
log4j.logger.org.springframework=WARN
# set mybatis log-level
log4j.logger.org.mybatis=WARN

14.9、资源文件,jdbc.properties

jdbc.driverClassName=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@127.0.0.1:1521:XE
jdbc.username=briup
jdbc.password=briup
jdbc.initialSize=5
jdbc.maxActive=10

14.10、dao层配置文件,spring-dao.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 读取资源文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
​
<!-- 配置数据源 -->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}">
</property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="initialSize" value="${jdbc.initialSize}"></property>
<property name="maxActive" value="${jdbc.maxActive}"></property>
</bean>
​
<!-- 配置sqlSessionFactoryBean到spring容器中 -->
<bean name="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="typeAliasesPackage" value="com.briup.bean"></property>
<property name="mapperLocations"
value="classpath:com/briup/mapper/*Mapper.xml"></property>
</bean>
<!-- 让spring扫描映射接口所在包 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.briup.dao"></property>
</bean>
</beans>

14.11、service配置文件,spring-service.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-
beans.xsd
http://www.springframework.org/schema/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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.briup.service"></context:component-scan>
<!-- 配置事务管理器 -->
<bean name="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务通知(拦截器) -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" rollback-for="Exception"
timeout="5"/>
<tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!-- 事务AOP配置 -->
<aop:config>
<aop:pointcut expression="execution(* com.briup.service..*.*(..))"
id="txPointCut"/>
<!-- advisor可以将已经存在的advice和pointcut结合起来 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>

14.12、web层配置文件,spring-web-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 指定spring需要扫描的包路径 -->
<context:component-scan base-package="com.briup.web.controller">
</context:component-scan>
<!-- 开启springmvc的注解功能 -->
<mvc:annotation-driven>
<!-- 配置消息转换器,指定返回的字符编码 -->
<mvc:message-converters>
<bean
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=utf-8</value>
<value>text/html;charset=utf-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 视图解析器 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"></property>
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 处理静态资源 -->
<mvc:default-servlet-handler />
<!-- 配置指定路径对应的逻辑视图名 -->
<mvc:view-controller path="/ajax" view-name="ajax"/>
</beans>

14.13、dao层和service层的总配置文件,applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-
beans.xsd
http://www.springframework.org/schema/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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<import resource="spring-dao.xml"/>
<import resource="spring-service.xml"/>
</beans>

14.14、web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 配置编码过滤器 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-
class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置springmvc的前端控制器 -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-
class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-web-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 配置spring监听器,在服务器启动期间就会读取spring的配置文件或配置类,从而创建spring
容器对象 -->
<listener>
<listener-
class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置spring启动时要读取的配置位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
</web-app>

14.15、/WEB-INF/jsp/ajax.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>hello</title>
<script type="text/javascript" src="/static/js/jquery-1.8.3.min.js"></script>
<script type="text/javascript">
$(function(){
$("#btn").on("click",function(){
var json = {
name:"tom",
age:20,
salary:2000
};
$.ajax({
type:"post",
url:"/teacher/test",
contentType:"application/json",
data:JSON.stringify(json),
dataType:"text",
success:function(data){
$("#dd").html("").html(data).hide().show(2000);
}
});
});
});
</script>
</head>
<body>
<button id="btn">ajax</button>
<div id="dd"></div>
</body>
</html>

14.16、设置项目的ContextPath为/

14.17、测试

  • 访问地址:http://127.0.0.1:8989/ajax

  • 注意,可以在service层,抛出异常,查看事务是否会回滚

  • 注意,Controller中的参数上,一定要加注解 @RequestBody

14.18、总结

  • 搭建过程中的注意事项:

  1. SpringMVC和Spring

    • 不需要什么特殊的配置就可以结合

  2. MyBatis和Spring

    • 需要引入额外的jar包:mybatis-spring-1.x.x.jar

    • 配置数据源到spring容器

    • 把MyBatis中的SqlSessionFactory配置给Spring容器

    • 在spring中配置需要扫描的MyBatis映射接口所在包的位置

  3. Spring中配置SqlSessionFactory

    • 可以在MyBatis的mybatis-config.xml中把MyBatis的信息配好,然后再让spring读取这个文件

    • 可以删除mybatis-config.xml文件,然后MyBatis的信息都配置到Spring中(常用)

  4. spring配置文件中的重要信息

    • 可以写在外部的资源文件中,然后再使用spring的标签读出来使用<context:property-placeholder location="classpath:jdbc.properties"/>${jdbc.username}

  5. 事务配置

    • 事务管理需要配置在service层方法上

    • 配置事务需要三步

    • 配置事务管理器(使用jdbc的事务管理器)

    • 配置事务拦截器(使用tx前缀的标签)

    • spring的aop配置(使用aop前缀的标签)

  6. 日志配置

    • 日志中可以屏蔽一些输出log4j.logger.org.springframework = ERROR

  7. web.xml文件配置

    • 配置编码过滤器

    • 配置spring读取的配置文件

    • 配置前端控制器

SpringMVC的前端控制器,读取配置文件,会生成一个容器,Spring的监听器读取配置文件,也会生成一个容器,即是两个容器SpringMVC容器【继承了】Spring容器,这俩个容器的关系是:

* 使用xml或者注解进行配置,springMVC或者spring读取配置信息之后,会把配置的对象(就是spring中的bean)放到容器中进行管理
* 服务器启动的时候,SpirngMVC中的前端控制器会读取配置文件,把相关配置的对象放到自己产生的容器中进行管理(需要在web.xml配置) 
* 服务器启动的时候spring也会读取配置文件,把相关配置的对象放到自己产生的容器中进行管理(需要在web.xml配置) 
* SpirngMVC创建容器中所管理的Bean一般是只对SpringMVC有效,如Controller、HandlerMapping、HandlerAdapter等等(因为它一般只读取SpringMVC的配置文件) 
* Spirng创建容器中所管理的Bean一般是对于整个应用程序共享的,一般如DAO层、Service层Bean。(因为它一般只读取service层和dao层的配置文件) 
* SpirngMVC创建的容器 【继承了】 Spirng创建的容器 
* 子容器可以从父容器中拿出bean来使用,但是父容器不能从子容器中拿bean来使用。所以在Controller中可以注入service层的实现类对象,Controller在SpringMVC创建的容器中,service是在Spring创建的容器中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值