一,springmvc基本概念

1.三层架构

开发架构一般基于两种形式,一种是c/s架构,也就是客户端服务器,另一种是b/s架构,也就是浏览器服务器。javaee的开发基本都是b/s架构。在b/s架构中,系统表转的三层架构包括:表现层,业务层,持久层。

  • 表现层:web层。负责接收客户端请求,向客户端响应结果。依赖于业务层,接受请求调用业务层进行业务处理,并将处理结果响应回客户端。
  1. 展示层:展示结果。
  2. 控制层:接受请求

表现层的设计一般都是使用MVC设计模式。

  • 业务层:service层。负责业务逻辑处理。

业务层可能会依赖于持久层,如果需要对数据持久化,需要保证事物的一致性。

  • 持久层:dao层。负责数据持久化。
  1. 数据库:对数据进行持久化的载体。
  2. 数据访问层:业务层和持久层交互的接口

持久层就是和数据库交互,对数据库表进行增删改查。

2.MVC模型

MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种用于设计创建 Web 应用程序表现层的模式。MVC 中每个部分各司其职:

  • Model(模型):通常指的就是我们的数据模型。作用一般情况下用于封装数据。
  • View(视图):通常指的就是我们的 jsp 或者 html。作用一般就是展示数据的。通常视图是依据模型数据创建的。
  • Controller(控制器):是应用程序中处理用户交互的部分。作用一般就是处理程序逻辑的。

3.springmvc是什么

SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于 Spring
FrameWork 的后续产品,已经融合在 Spring Web Flow 里面。Spring 框架提供了构建 Web 应用程序的全功
能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用 Spring 进行 WEB 开发时,可以选择使用 Spring
的 Spring MVC 框架或集成其他 MVC 开发框架,如 Struts1(现在一般不用),Struts2 等。

SpringMVC 已经成为目前最主流的 MVC 框架之一,并且随着 Spring3.0 的发布,全面超越 Struts2,成
为最优秀的 MVC 框架。

它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持
RESTful 编程风格的请求。

4.springmvc和struts2的优劣对比

共同点:

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

区别:

  1. Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
  2. Spring MVC 是基于方法设计的,而 Struts2 是基于类,Struts2 每次执行都会创建一个动作类。所

以 Spring MVC 会稍微比 Struts2 快些。

  1. Spring MVC 使用更加简洁,同时还支持 JSR303, 处理 ajax 的请求更方便

(JSR303 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注
解加在我们 JavaBean 的属性上面,就可以在需要校验的时候进行校验了。)

  1. Struts2 的 OGNL 表达式使页面的开发效率相比 Spring MVC 更高些,但执行效率并没有比 JSTL 提

升,尤其是 struts2 的表单标签,远没有 html 执行效率高。

二,Springmvc入门

1.入门案例

需求:

点击页面超链接,跳转到成功页面

  1. <a href="success/success">testSuccess</a><br/>
@Controller
@RequestMapping("/success")
public class success {
    /**
     * 入门案例
     * @return
     */
    @RequestMapping("/success")
    public String testSuccess(){
        System.out.println("testSuccess()...");
        return "success";
    }
    }

web.xml

配置核心控制器:一个Servlet

 <!-- 配置 DispatcherServlet -->
  <servlet>
    <servlet-name>springDispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!-- 映射地址 -->
  <servlet-mapping>
    <servlet-name>springDispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

springmvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation=" http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
       <!--组件扫描-->
    <context:component-scan base-package="com.atguigu"></context:component-scan>
    <!--配置视图解析器-->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--前缀解析器-->
        <property name="prefix" value="/WEB-INF/views/"></property>
        <!--后缀解析器-->
        <property name="suffix" value=".jsp"></property>
    </bean>
    <!--开启注解支持-->
    <mvc:annotation-driven />
    </beans>

2.入门案例流程分析

  1. 服务器启动,加载应用。读取web.xml中的配置创建spring容器并且初始化容器中的对象。从案例中可以发现创建的是successController和InternalResourceViewResolver,实际上远不止这些。
  2. 浏览器发送请求,经过前端控制器,被其捕获,他并不处理请求,只是根据路径的URL匹配有没有对应的,如果匹配到@RequestMapping中的内容,就转发。
  3. 转发到控制层执行对应的方法,该方法有一个返回值。
  4. 根据方法的返回值,借助视图解析器对象找到对应的视图结果。
  5. 渲染结果视图,响应浏览器

3.请求响应流程

Spring[扩展]MVC使用篇 - 图1

请求相应流程:
    请求先来到springDispatcherServlet看Springmvc是否存在对应的映射?
        如果不存在,看springmvc的配置文件是否配置了<mvc:default-servlet-handler/>
            如果不存在,页面响应404,控制台打印no mapping found
            如果存在,转发到目标资源。
        如果存在,请求交给handlerMapping(处理器映射器),由它获取HandlerExecutionChain对象,
        再交给HandlerAdapter(处理器适配器)对象,调用拦截器的PreHandle方法,
        然后调用目标Handler方法得到ModelAndview对象,调用拦截器的PostHnadle方法,
        查看是否存在异常
            如果存在,由异常处理器处理异常,得到新的ModelAndView对象,
            如果不存在异常,由视图解析器解析视图,得到实际的View,渲染视图,调用拦截器的afterCompletion方法

4.案例中涉及的组件

  1. DispatcherServlet:前端控制器:用户请求到达前端控制器,它就相当于 mvc 模式中的 c,dispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet 的存在降低了组件之间的耦合性
  2. HandlerMapping:处理器映射器:HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
  3. Handler:处理器:开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由Handler 对具体的用户请求进行处理。
  4. HandlAdapter:处理器适配器:通过适配器对处理器进行执行,通过拓展适配器可以处理更多类型。
  5. View Resolver:视图解析器:View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名.即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户
  6. View:视图:SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。我们最常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
  7. mvc:annotation-driven说明:在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。使 用 mvc:annotation-driven 自动加载 RequestMappingHandlerMapping (处理映射器) 和RequestMappingHandlerAdapter ( 处 理 适 配 器 ) , 可 用 在 SpringMVC.xml 配 置 文 件 中 使 用mvc:annotation-driven/替代注解处理器和适配器的配置。

5.RequestMapping注解

源码:
@Target({ElementType.METHOD, ElementType.TYPE})//可以加到类上和方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping { }
作用:
    用于建立请求 URL 和处理请求方法之间的对应关系。
出现位置:
    1.类上
    请求 URL 的第一级访问目录,便于模块化管理。
    2.方法上
    请求 URL 的第二级访问目录。
属性:
    value:用于指定请求的 URL。它和 path 属性的作用是一样的。
    method:用于指定请求的方式。
    params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的 key 和 value 必须和
配置的一模一样。
例如:
params = {"accountName"},表示请求参数必须有 accountName
params = {"moeny!100"},表示请求参数中 money 不能是 100。
    headers:用于指定限制请求消息头的条件。
注意:
以上四个属性只要出现 2 个或以上时,他们的关系是与的关系。

三,请求参数的绑定

1.绑定的机制

表单中请求参数都是基于 key=value 的。
SpringMVC 绑定请求参数的过程是通过把表单提交请求参数,作为控制器中方法参数进行绑定的。
例如:
<a href="account/findAccount?accountId=10">查询账户</a>
中请求参数是:
    accountId=10
/**
* 查询账户
* @return
*/
@RequestMapping("/findAccount")
public String findAccount(Integer accountId) {
    System.out.println("查询了账户。。。。"+accountId);
    return "success";
}

2.支持的数据类型

  • 基本类型参数:

    包括基本类型和 String 类型

  • POJO 类型参数:

    包括实体类,以及关联的实体类

  • 数组和集合类型参数:

    包括 List 结构和 Map 结构的集合(包括数组)
    SpringMVC 绑定请求参数是自动实现的,但是要想使用,必须遵循使用要求。

3.使用要求

  1. 如果是基本类型或者 String 类型:

    要求我们的参数名称必须和控制器中方法的形参名称保持一致。(严格区分大小写)

  2. 如果是 POJO 类型,或者它的关联对象:

    要求表单中参数名称和 POJO 类的属性名称保持一致。并且控制器方法的参数类型是 POJO 类型。

  3. 如果是集合类型,有两种方式:

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

    它还可以实现一些数据类型自动转换。 如遇特殊类型转换要求,需要我们自己编写自定义类型转换器。

4.代码示例

   /**
     * 参数绑定1:
     * bean中包含bean
     */
    @RequestMapping("/getBean")
    public String getBean(Person person){
        System.out.println("getBean()...");
        System.out.println(person);
        return "success";
    }
    /**
     * 参数绑定2
     * 集合类型
     */
    @RequestMapping("/getCollection")
    public String getList(MyCollection collection){
        System.out.println("getList()....");
        System.out.println(collection);
        return "success";
    }
<form action="success/getBean" method="post">
  id:  <input type="text" name="id"/><br/>
   name: <input type="text" name="user.name"/><br/>
    age:<input type="text" name="user.age"/><br/>
    <input type="submit" value="submit"/><br/>
</form>
<form method="post" action="success/getCollection">
    name:  <input type="text" name="list[0].name"/><br/>
    age: <input type="text" name="list[0].age"/><br/>
    name: <input type="text" name="map[1].name"/><br/>
    age: <input type="text" name="map[1].age"/><br/>
    <input type="submit" value="submit"/>
</form>

5.请求参数乱码问题

idea控制台输出中文乱码:-Dfile.encoding=UTF-8

 <!-- 编码过滤器,必须放在web.xml最上面,防止缓存 -->
  <filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <!-- 此处/*代表过滤所有请求 -->
  </filter-mapping>

6.关于静态资源处理

在 springmvc 的配置文件中可以配置,静态资源不过滤:

<!-- location 表示路径,mapping 表示文件,**表示该目录下的文件以及子目录的文件 -->
<mvc:resources location="/css/" mapping="/css/**"/>
<mvc:resources location="/images/" mapping="/images/**"/>
<mvc:resources location="/scripts/" mapping="/javascript/**"/>

7.关于get请求

tomacat 对 GET 和 POST 请求处理方式是不同的,GET 请求的编码问题,要改 tomcat 的 server.xml

配置文件,如下:

<Connector connectionTimeout="20000" port="8080"
protocol="HTTP/1.1" redirectPort="8443"/>
改为:
<Connector connectionTimeout="20000" port="8080"
protocol="HTTP/1.1" redirectPort="8443"
useBodyEncodingForURI="true"/>
如果遇到 ajax 请求仍然乱码,请把:
useBodyEncodingForURI="true"改为 URIEncoding="UTF-8"
即可

8.自定义类型转换器

代码:

jsp页面

<form action="success/date" method="post">
    <input type="text" name="date"/><br/>
    <input type="submit" value="submit"/>
</form>

转换器

public class StringToDate implements Converter<String, Date> {
    @Override
    public Date convert(String s) {
        if (s==null||"".equals(s)){
            throw new RuntimeException("输入不能为空");
        }
        try {
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            return format.parse(s);
        } catch (Exception e) {
            throw new ClassCastException("类型转换异常");
        }

    }
}

自定义类型转换器

spring 配置类型转换器的机制是,将自定义的转换器注册到类型转换服务中去。

<!--自定义类型转换-->
<!-- 配置类型转换器工厂 -->
    <bean id="conversionServiceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <!-- 给工厂注入一个新的类型转换器 -->
        <property name="converters">
            <set><!-- 配置自定义类型转换器 -->
                <bean class="com.atguigu.utils.StringToDate"></bean>
            </set>
        </property>
    </bean>
<mvc:annotation-driven conversion-service="conversionServiceFactoryBean"/>

控制器

 /**
     * 自定义数据类型转换
     * 1.编写类型转换类,实现 Converter 接口,该接口有两个泛型。
     * 2.配置文件中配置转换器,并在注解支持里面注册。
     * 3.控制层参数列表传入转换类
     */
    @RequestMapping("/date")
    public String getDate(Date date){
        System.out.println(date.toString());
        return "success";
    }

9.使用servletAPI对象作为方法参数

springMVC 还支持使用原始 ServletAPI 对象作为控制器方法的参数。支持原始 ServletAPI 对象有:

    /**
     * 获取原生ServletAPI
     */
    @RequestMapping("/getServlet")
    public String getServlet(HttpServletRequest request, HttpServletResponse response){
        System.out.println("getServlet()...");
        return "success";
    }
<a href="success/getServlet">getServlet</a><br/>

四,常用注解

1.RequestParam

作用:
把请求中指定名称的参数给控制器中的形参赋值。
属性:
value:请求参数中的名称。
required:请求参数中是否必须提供此参数。默认值:true。表示必须提供,如果不提供将报错。

/**
     * @RequestParam 当表单name和形参列表名字不一致时,使用此注解。
     * 属性:
     * name:指定表单的name
     * required:指定是否为必须
     */
    @RequestMapping("/test1")
    public String test1(@RequestParam(name = "name", required = false) String username, Integer age) {
        System.out.println(username + " " + age);
        return "success";
    }
<form method="post" action="anno/test1">
    <input type="text" name="name"/><br/>
    <input type="text" name="age"/><br/>
    <input type="submit" value="submit"/>
</form>

2.RequestBody

作用:
用于获取请求体内容。直接使用得到是 key=value&key=value…结构的数据。
get 请求方式不适用。
属性:
required:是否必须有请求体。默认值是:true。当取值为 true 时,get 请求方式会报错。如果取值
为 false,get 请求得到是 null。

/**
     * @RequestBody 用于获取请求体内容
     * 属性:
     * 属性:
     * required:指定是否为必须
     * 当请求方式为get时,指定为true会报错,
     * 指定为false时,拿到的是null。
     */
    @RequestMapping("/test2")
    public String test2(@RequestBody(required = false) String name) {
        System.out.println(name);
        return "success";
    }
<form method="post" action="anno/test2">
    <input type="text" name="name"/><br/>
    <input type="submit" value="submit"/>
</form>

3.PathVariable

作用:
用于绑定 url 中的占位符。例如:请求 url 中 /delete/{id},这个{id}就是 url 占位符。
url 支持占位符是 spring3.0 之后加入的。是 springmvc 支持 rest 风格 URL 的一个重要标志。
属性:
value:用于指定 url 中占位符名称。
required:是否必须提供占位符。

<a href="anno/test3/张三">test3</a><br/>
    /**
     * @PathVariable restful风格,spring3.0新特性
     * 以/参数方式传递参数
     * 次注解的value属性指定传递的参数名
     */
    @RequestMapping("/test3/{name}")
    public String test3(@PathVariable("name") String name) {
        System.out.println(name);
        return "success";
    }

关于rest风格url

*什么是 rest:
REST(英文:Representational State Transfer,简称 REST)描述了一个架构样式的网络系统,
比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,他是 HTTP 规范的主要编写者之
一。在目前主流的三种 Web 服务交互方案中,REST 相比于 SOAP(Simple Object Access protocol,简单
对象访问协议)以及 XML-RPC 更加简单明了,无论是对 URL 的处理还是对 Payload 的编码,REST 都倾向于用更
加简单轻量的方法设计和实现。值得注意的是 REST 并没有一个明确的标准,而更像是一种设计的风格。
它本身并没有什么实用性,其核心价值在于如何设计出符合 REST 风格的网络接口。
*restful 的优点
它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。
*restful 的特性:
*资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。
它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个 URI(统一
资源定位符)指向它,每种资源对应一个特定的 URI 。要
获取这个资源,访问它的 URI 就可以,因此 URI 即为每一个资源的独一无二的识别符。 表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层 (Representation)。
比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二
进制格式。
*状态转化(State Transfer):每 发出一个请求,就代表了客户端和服务器的一次交互过程。
*HTTP 协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,
必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以
就是 “表现层状态转化”。具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET 、POST 、PUT、
DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来
删除资源。
*restful 的示例:
    /account/1 HTTP GET : 得到 id = 1 的 account 
    /account/1 HTTP DELETE: 删除 id = 1 的 account 
    /account/1 HTTP PUT: 更新 id = 1 的 account
    /account HTTP POST: 新增 account

4.requestHeader

作用:
用于获取请求消息头。
属性:
value:提供消息头名称
required:是否必须有此消息头
注:
在实际开发中一般不怎么用。

    /**
     * @RequestHeader 获取请求头信息,不常用
     * 属性:
     * value:
     * required:
     */
    @RequestMapping("/test4")
    public String test4(@RequestHeader(value = "Accept-Language", required = false) String value) {
        System.out.println(value);
        return "success";
    }
<a href="anno/test4">test4</a><br/>

5.CookieValue

作用:
用于把指定 cookie 名称的值传入控制器方法参数。
属性:
value:指定 cookie 的名称。
required:是否必须有此 cookie。

    /**
     * @CookieValue 获取cookie里面的信息
     * 属性:
     * value:
     * required:
     */
    @RequestMapping("/test5")
    public String test5(@CookieValue(value = "JSESSIONID", required = false) String value) {
        System.out.println(value);
        return "success";
    }
<a href="anno/test5">test5</a><br/>

6.ModelAttribute

作用:
该注解是 SpringMVC4.3 版本以后新加入的。它可以用于修饰方法和参数。
出现在方法上,表示当前方法会在控制器的方法执行之前,先执行。它可以修饰没有返回值的方法,也可
以修饰有具体返回值的方法。
出现在参数上,获取指定的数据给参数赋值。
属性:
value:用于获取数据的 key。key 可以是 POJO 的属性名称,也可以是 map 结构的 key。
应用场景:
当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。
例如:
我们在编辑一个用户时,用户有一个创建信息字段,该字段的值是不允许被修改的。在提交表单数
据是肯定没有此字段的内容,一旦更新会把该字段内容置为 null,此时就可以使用此注解解决问题。

 /**
     * @ModelAttribute
     * 1.夹在方法上:
     *      1.没有返回值
     *      可以应用在从表单获取的值不全,在返回方法前先给其将值补全
     *      2.有返回值
     *      可以根据表单提交的一个值在到达控制层之前先去从数据库查询,
     *      *严重怀疑这个方法利用动态代理对方法进行增强。
     * 2.加在方法参数上:
     *      可以为指定的属性赋值
     *
     */
    @RequestMapping("/test6")
    public String test6(User user) {
        System.out.println(user.getName() + "..." + user.getAge());
        return "success";
    }
    @ModelAttribute
    public void test11(User user){
        System.out.println(user.getName()+" "+user.getAge()+"......");
        user.setAge(20);
    }
<form action="anno/test6">
    <input type="text" name="name"/><br/>
    <input type="submit" value="submit"/>
</form>

场景二:

 @RequestMapping("/test6")
    public String test6(User user) {
        System.out.println(user.getName() + "..." + user.getAge());
        return "success";
    }
   @ModelAttribute
    public User test22(String name) {
        //模拟从数据库查询数据
        User user=findUserByname(name);
        return user;
    }  
//模拟服务层dao层方法
    private User findUserByname(String name) {
        User user = new User();
        user.setName(name);
        user.setAge(20);
        return user;
    }
<form action="anno/test6">
    <input type="text" name="name"/><br/>
    <input type="submit" value="submit"/>
</form>

场景三

    //模拟服务层dao层方法
    private User findUserByname(String name) {
        User user = new User();
        user.setName(name);
        user.setAge(20);
        return user;
    }
    @RequestMapping("/test7")
    public String test7(@ModelAttribute(value="1") User user){
        System.out.println(user);
        return "success";
    }
    @ModelAttribute
    public void test33(String name, Map<String,User>map){
        //模拟从数据库查询
        User user=findUserByname(name);
        map.put("1",user);
    }
<form action="anno/test7">
    <input type="text" name="name"/><br/>
    <input type="submit" value="submit"/>
</form>

7.SessionAttribute

作用:
用于多次执行控制器方法间的参数共享。
属性:
value:用于指定存入的属性名称
type:用于指定存入的数据类型

<a href="session/put">put</a><br/>
<a href="session/get">get</a><br/>
<a href="session/delete">delete</a><br/>
@Controller
@RequestMapping("/session")
@SessionAttributes(value = {"name","age"})
public class Session {
    /**
     * 存入Session
     *  Model 是 spring 提供的一个接口,该接口有一个实现类 ExtendedModelMap
     *  该类继承了 ModelMap,而 ModelMap 就是 LinkedHashMap 子类
     */
    @RequestMapping("/put")
    public String put(Model model){
        model.addAttribute("name","尹会东");
        model.addAttribute("age",23);
        return "success";
    }
    /**
     * 取出Session
     */
    @RequestMapping("/get")
    public String get(ModelMap map){
        System.out.println(map.get("name"));
        System.out.println(map.get("age"));
        return "success";
    }
    /**
     * 清除Session
     */
    @RequestMapping("/delete")
    public String delete(SessionStatus status){
        status.setComplete();
        return "success";
    }
}

五,响应数据和结果视图

1.返回值分类

①void

在 controller 方法形参上可以定义 request 和 response,使用 request 或 response 指定响应结果:

1、使用 request 转向页面,如下:
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, 
response);
2、也可以通过 response 页面重定向:
response.sendRedirect("testRetrunString") 3、也可以通过 response 指定响应结果,例如响应 json 数据:
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("json 串");

②ModelAndView

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

该对象中有两个方法:

③转发和重定向

forward 转发

controller 方法在提供了 String 类型的返回值之后,默认就是请求转发。

需要注意的是,如果用了 formward:则路径必须写成实际视图 url,不能写逻辑视图。

它相当于“request.getRequestDispatcher(“url”).forward(request,response)”。使用请求转发,既可以转发到 jsp,也可以转发到其他的控制器方法。

Redirect 重定向

contrller 方法提供了一个 String 类型返回值之后,它需要在返回值里使用:redirect:它相当于“response.sendRedirect(url)”。需要注意的是,如果是重定向到 jsp 页面,则 jsp 页面不能写在 WEB-INF 目录中,否则无法找到

@Controller
@RequestMapping("/return")
public class returnController {
    /**
     * 返回值类型为String,在request作用于存放值,并显示到页面
     * @param model
     * @return
     */
    @RequestMapping("/string")
    public String test1(Model model){
        model.addAttribute("name","尹会东");
        model.addAttribute("age",20);
        return "success";
    }
    /**
     * 返回值类型为void
     */
    @RequestMapping("/void")
    public void test2(HttpServletResponse response, HttpServletRequest request)throws Exception{
//      //request.getRequestDispatcher("/WEB-INF/views/success.jsp").forward(request,response);
        //response.sendRedirect("/index.jsp");
        response.getWriter().write("aaaa");
    }
    /**
     * 返回值类型为ModelAndView
     */
    @RequestMapping("/model")
    public ModelAndView test3(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("name","zhangsan");
        mv.addObject("age",20);
        mv.setViewName("success");
        return mv;
    }
    /**
     * 关键字:forward和redirect
     */
    @RequestMapping("/fr")
    public String test4(){
        System.out.println("..................");
        //return "forward:/WEB-INF/views/success.jsp";
        return "redirect:/index.jsp";
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>return</title>
</head>
<body>
<h3>return返回值类型</h3>
<a href="return/string" >string</a><br/>
<a href="return/void" >void</a><br/>
<a href="return/model" >model</a><br/>
<a href="return/fr" >fr</a><br/>
</body>
</html>

@ResponseBody 注解响应json数据

作用:

该注解用于将 Controller 的方法返回的对象,通过 HttpMessageConverter 接口转换为指定格式的数据如:json,xml 等,通过 Response 响应给客户端

@Controller
@RequestMapping("/ajax")
public class Ajax {
    /**
     * 发送Ajax异步请求
     * 1.静态资源处理:在配置文件中加入<mvc:resource />标签,指定放行的资源。
     * 2.导入jackson的依赖
     * 3.
     *      @RequestBody
     *      接受请求体消息
     *      @ResponseBody
     *      发送响应体消息
     * 4.springmvc框架已经为我们封装好了处理json数据的方法,底层会自动执行。
     * @param user
     * @return
     */
    @RequestMapping("/ajax")
    public @ResponseBody
    User testAjax(@RequestBody User user) {
        user.setName("yinhuidong");
        user.setAge(23);
        return user;
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Ajax</title>
    <script src="js/jquery-3.3.1.min.js"></script>
    <script type="text/javascript">
        $(function () {
            $("#btn").click(function () {
                $.ajax({
                    url: "ajax/ajax",
                    contentType:"application/json;charset=UTF-8",
                    data: '{"name":"aa","age":20}',
                    dataType: "json",
                    type: "post",
                    success: function (data) {
                        alert(data.name);
                        alert(data.age);
                    }
                });
            });
        });
    </script>
</head>
<body>
<input type="button" id="btn" value="别点我"/>
</body>
</html>
//data:JSON.stringify({"name":"张三","msg":message}),
data:'{"name":"张三","msg":"123"}',

六,文件上传和下载

1.文件上传

前提条件

  1. form 表单的 enctype 取值必须是:multipart/form-data

         (默认值是:application/x-www-form-urlencoded)<br />    enctype:是表单请求正文的类型
    
  2. method 属性取值必须是 Post

  3. 提供一个文件选择域

原理分析

当 form 表单的 enctype 取值不是默认值后,request.getParameter()将失效。 
enctype=”application/x-www-form-urlencoded”时,form 表单的正文内容是:
         key=value&key=value&key=value
当 form 表单的 enctype 取值为 Mutilpart/form-data 时,请求正文内容就变成:
     每一部分都是 MIME 类型描述的正文
-----------------------------7de1a433602ac 分界符
Content-Disposition: form-data; name="userName" 协议头
aaa 协议的正文
-----------------------------7de1a433602ac
Content-Disposition: form-data; name="file"; 
filename="C:\Users\zhy\Desktop\fileupload_demofile\b.txt"
Content-Type: text/plain 协议的类型(MIME 类型)
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
-----------------------------7de1a433602ac--

①传统模式的文件上传

<h3>文件上传</h3>
<form action="file/fileupload" method="post" enctype="multipart/form-data">
    择文件:<input type="file" name="upload"/><br/>
    <input type="submit" value="上传文件"/>
</form>
<hr/>
@Controller
@RequestMapping("/file")
public class upload {
    /**
     * 文件上传1:
     * 传统的文件上传
     *
     */
    @RequestMapping(value = "/fileupload")
    public String fileupload(HttpServletRequest request) throws Exception {
        // 先获取到要上传的文件目录
        String path = request.getSession().getServletContext().getRealPath("/uploads");
        // 创建File对象,一会向该路径下上传文件
        File file = new File(path);
        // 判断路径是否存在,如果不存在,创建该路径
        if (!file.exists()) {
            file.mkdirs();
        }
        // 创建磁盘文件项工厂
        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload fileUpload = new ServletFileUpload(factory);
        // 解析request对象
        List<FileItem> list = fileUpload.parseRequest(request);
        // 遍历
        for (FileItem fileItem : list) {
            // 判断文件项是普通字段,还是上传的文件
            if (fileItem.isFormField()) {
            } else {
                // 上传文件项
            }
            // 获取到上传文件的名称
            String filename = fileItem.getName();
            // 上传文件
            fileItem.write(new File(file, filename));
            // 删除临时文件
            fileItem.delete();
        }
        return "success";
    }
    }

②springmvc文件上传

<form action="file/upload" method="post" enctype="multipart/form-data">
    择文件:<input type="file" name="upload"/><br/>
    <input type="submit" value="上传文件"/>
</form>
<hr/>
   /**
     * springmvc文件上传
     * 1.导入依赖
     * commons-upload
     * commons-io
     * 2.配置文件解析器
     * 3.编写jsp页面
     * 4.代码实现
     */
    @RequestMapping("/upload")
    public String fileupload2(HttpSession session, MultipartFile upload)throws Exception{
        //获取文件上传路径
        String path = session.getServletContext().getRealPath("/img");
        File file = new File(path);
        //判断不存在该目录就创建
        if (!file.exists()){
            file.mkdirs();
        }
        //获取文件名
        String filename = upload.getOriginalFilename();
        System.out.println(filename);
        //起别名
        String s = UUID.randomUUID().toString().replace("_", "").toUpperCase();
        filename=s+filename;
        //开始上传
        upload.transferTo(new File(file,filename));
        return "success";
    }

2.文件下载

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h3>文件下载</h3>
<a href="down/down?name=6.jpg">点击下载</a>
</body>
</html>
@Controller
@RequestMapping("/down")
public class down {
    /**
     * 文件下载
     * 1.获取文件名
     * 2.获得文件下载路径
     * 3.拼接
     * 4.用流来加载文件到字节数组
     * 5.设置头信息以附件形式打开
     * 6.设置响应状态吗
     * 7.下载
     */
    @RequestMapping("/down")
    public ResponseEntity<byte[]> down(HttpSession session,String name)throws Exception{
        //获取文件下载路径
        String path = session.getServletContext().getRealPath("/img");
        //加上文件名
        String finalpath = path + File.separator + name;
        FileInputStream is = new FileInputStream(finalpath);
        //is.available()获取流的字节数
        byte[] bytes = new byte[is.available()];
        is.read(bytes);
        HttpHeaders headers = new HttpHeaders();
        //设置有附件形式打开
        headers.add("Content-Disposition", "attachment;filename="+name);
        //设置响应吗
        HttpStatus status=HttpStatus.OK;
        ResponseEntity<byte[]> entity = new ResponseEntity<>(bytes, headers, status);
        is.close();
        return entity;
    }
}

七,springmvc中的异常处理

1.异常处理的思路

系统中异常包括两类:预期异常和运行时异常 RuntimeException,前者通过捕获异常从而获取异常信息,
后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。

系统的 dao、service、controller 出现都通过 throws Exception 向上抛出,最后由 springmvc 前端
控制器交由异常处理器进行异常处理。

2.代码

自定义异常类

/**
 * @author yinhuidong
 * @createTime 2020-03-09-17:18
 */
public class MyException extends Exception{
    private String message;

    public MyException() {
    }

    public MyException(String message) {
        this.message = message;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
/**
 * 自定义异常处理器
 * @author yinhuidong
 * @createTime 2020-03-09-17:20
 */
public class HandlerException implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        MyException mye=null;
        if (e instanceof MyException){
            mye= (MyException) e;
        }else{
            mye=new MyException();
        }
        mye.setMessage("系统繁忙,请稍后再试!");
        ModelAndView mv = new ModelAndView();
        mv.addObject("message",mye.getMessage());
        mv.setViewName("/error");
        return mv;
    }
}
<!--异常处理类-->
    <bean id="handlerException" class="com.atguigu.exception.HandlerException"></bean>
/**
 * @author yinhuidong
 * @createTime 2020-03-09-17:27
 */
@Controller
@RequestMapping("/exception")
public class TestException {
    /**
     * 测试异常
     * 1.编写自定义异常类,继承Exception类
     * 2.编写异常处理类,
     * 3.在配置文件中配置异常处理类
     * 4.模拟发生异常
     * @return
     */
    @RequestMapping("/test")
    public String test1()throws MyException {
        int i=1/0;
        return "success";
    }
}
<a href="exception/test">测试异常</a>

全局异常处理器

public class ExceptionHandler implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException
            (HttpServletRequest request,
             HttpServletResponse response, Object o, Exception e) {

        boolean isAjax = JudgeRequestType.judgeIsAjax(request);
        if (isAjax){
            try {
                String message=e.getMessage();

                Gson gson = new Gson();
                String json = gson.toJson(message);
                response.getWriter().write(json);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            return null;
        }
        ModelAndView mv = new ModelAndView();
        mv.addObject("message",e.getMessage());
        mv.setViewName("error");
        return mv;
    }
}
class JudgeRequestType {

    public static boolean judgeIsAjax(HttpServletRequest request){
        String accept = request.getHeader("Accept");
        String header = request.getHeader("X-Requested-With");
        return
                (accept!=null &&accept.length()>0&&accept.contains("application/json"))
                        ||
                        (header!=null&&header.length()>0&&header.equals("XMLHttpRequest"));
    }
}

八,springmvc中的拦截器

拦截器的作用

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

谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺
序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。

说到这里,可能大家脑海中有了一个疑问,这不是我们之前学的过滤器吗?是的它和过滤器是有几分相似,但
是也有区别,接下来我们就来说说他们的区别:

  1. 过滤器是 servlet 规范中的一部分,任何 java web 工程都可以使用。
  2. 拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
  3. 过滤器在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。
  4. 拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦

截的。

它也是 AOP 思想的具体应用。我们要想自定义拦截器, 要求必须实现:HandlerInterceptor 接口。

自定义拦截器步骤

1.写一个类继承HandlerInterceptor接口

/**
 * @author yinhuidong
 * @createTime 2020-03-09-17:33
 */
public class Intercepter1 implements HandlerInterceptor {
    /**
     * 1. preHandle方法是controller方法执行前拦截的方法
     * 1. 可以使用request或者response跳转到指定的页面
     * 2. return true放行,执行下一个拦截器,如果没有拦截器,
     *    执行controller中的方法。
     * 3. return false不放行,不会执行controller中的方法
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle()....");
        //request.getRequestDispatcher("/WEB-INF/views/error.jsp").forward(request, response);
        return true;
    }

    /**
     * 2. postHandle是controller方法执行后执行的方法,在JSP视图执行前。
     * 1. 可以使用request或者response跳转到指定的页面
     * 2. 如果指定了跳转的页面,那么controller方法跳转的页面将不会显示。
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */

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

    /**
     * 3. postHandle方法是在JSP执行后执行
     *   request或者response不能再跳转页面了
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */

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

2.在springmvc的配置文件中配置拦截器

<!--配置拦截器-->
    <mvc:interceptors>
        <!--配置一个拦截器-->
        <mvc:interceptor>
            <!--设置拦截路径-->
            <mvc:mapping path="/**"/>
            <!--设置哪些不拦截-->
            <!--<mvc:exclude-mapping path=""/>-->
            <!--配置bean-->
            <bean class="com.atguigu.intercept.Intercepter1"/>
        </mvc:interceptor>
        <mvc:interceptor>
            <!--设置拦截路径-->
            <mvc:mapping path="/**"/>
            <!--设置哪些不拦截-->
            <!--<mvc:exclude-mapping path=""/>-->
            <!--配置bean-->
            <bean class="com.atguigu.intercept.Intercepter2"/>
        </mvc:interceptor>
    </mvc:interceptors>

3.编写测试类和jsp页面

/**
 * @author yinhuidong
 * @createTime 2020-03-09-17:37
 */
@Controller
@RequestMapping("/intercepter")
public class TestIntercepter {
    /**
     * 1.编写拦截器
     * 2.在配置文件中配置拦截器
     * 3.测试
     * 4.多个拦截器执行顺序
     */
    @RequestMapping("/test")
    public String test(){
        System.out.println("controller().....");
        return "success";
    }
}
<a href="intercepter/test">测试拦截器</a>

4.定义多个拦截器

/**
 * @author yinhuidong
 * @createTime 2020-03-09-17:33
 */
public class Intercepter2 implements HandlerInterceptor {
    /**
     * 1. preHandle方法是controller方法执行前拦截的方法
     * 1. 可以使用request或者response跳转到指定的页面
     * 2. return true放行,执行下一个拦截器,如果没有拦截器,
     *    执行controller中的方法。
     * 3. return false不放行,不会执行controller中的方法
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle2()....");
        //request.getRequestDispatcher("/WEB-INF/views/error.jsp").forward(request, response);
        return true;
    }

    /**
     * 2. postHandle是controller方法执行后执行的方法,在JSP视图执行前。
     * 1. 可以使用request或者response跳转到指定的页面
     * 2. 如果指定了跳转的页面,那么controller方法跳转的页面将不会显示。
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */

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

    /**
     * 3. postHandle方法是在JSP执行后执行
     *   request或者response不能再跳转页面了
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */

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

拦截器的简单应用

需求:

1、有一个登录页面,需要写一个 controller 访问页面 
2、登录页面有一提交表单的动作。需要在 controller 中处理。
    2.1、判断用户名密码是否正确
    2.2、如果正确 向 session 中写入用户信息
    2.3、返回登录成功。
3、拦截用户请求,判断用户是否登录
    3.1、如果用户已经登录。放行
    3.2、如果用户未登录,跳转到登录页面

代码实现:

1.控制器代码

//登陆页面
@RequestMapping("/login")
public String login(Model model)throws Exception{
    return "login"; 
}
//登陆提交
//userid:用户账号,pwd:密码
@RequestMapping("/loginsubmit")
public String loginsubmit(HttpSession session,String userid,String pwd)throws Exception{
    //向 session 记录用户身份信息
    session.setAttribute("activeUser", userid);
    return "redirect:/main.jsp"; 
}
//退出
@RequestMapping("/logout")
public String logout(HttpSession session)throws Exception{
    //session 过期
    session.invalidate();
    return "redirect:index.jsp";
}

2.拦截器代码

public class LoginInterceptor implements HandlerInterceptor{
@Override
Public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
    //如果是登录页面则放行
    if(request.getRequestURI().indexOf("login.action")>=0){
        return true;
    }
    HttpSession session = request.getSession();
    //如果用户已登录也放行
    if(session.getAttribute("user")!=null){
        return true;
    }
    //用户没有登录挑战到登录页面
    request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
        return false;
    } 
}