SpringMVC学习笔记

1.SpringMVC的基本概念

学习黑马传智播客视频所记录的笔记

1.1 三层架构和MVC

三层架构指的是 web层,业务层,持久化层。
MVC只是把web层又细分了三层:

  • model(模型):JavaBean
  • View(视图):JSP
  • Controller(控制层):Servlet

1.2 SpringMVC是什么

引用百度

Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建
Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC
架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的Spring
MVC框架或集成其他MVC开发框架,如Struts1(现在一般不用),Struts 2(一般老项目使用)等等。

1.3 SpringMVC在三层架构的那一层

在这里插入图片描述

1.4 SpringMVC的优势

1、清晰的角色划分:

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

1.5 SpringMVC 和 和 Struts2 区别

共同点:

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

区别:

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

2. SpringMvc的基本概念及入门

在这里插入图片描述

2.1 搭建开发的环境

(1)创建maven项目
使用骨架,选择webapp。创建输入项目坐标

(2)解决maven项目创建过慢的问题

在这里插入图片描述

(3)补全maven目录

在这里插入图片描述

(4)引入jar包依赖

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

<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
  </dependency>
  <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.0</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

(5)在web.xml中配置前端控制器(servlet)web.xml约束

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
      version="3.1">   

在这里插入图片描述

(6)在resources目录下创建springmvc的配置文件

在这里插入图片描述

(7)配置tomcat服务器

在这里插入图片描述

2.2 编写入门程序

(1)重新创建index.jsp,因为带的index.jsp没有头信息,会中文乱码。

(2)编写一个HelloController 控制器类

在这里插入图片描述

(3)在springmvc配置文件中引入spring的约束,开启注解扫描

<?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.itheima"/>
    
</beans>

(4)在控制器类上添加注解,将控制器交给spring的IOC容器管理。

在这里插入图片描述

(5)在控制器类中的方法上加上注解,使其执行。

在这里插入图片描述

(6)加载springmvc配置文件。
在web.xml中的servlet中配置

在这里插入图片描述

/ 拦截所有的静态资源,不包括转发的JSP
/* 拦截所有的资源,包括转发的JSP,这是错误的不能用。

(7)springmvc配置文件中配置视图解析器及注解支持

<!--配置视图解析器对象-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!--去这个文件下找对应的页面-->
    <property name="prefix" value="/WEB-INF/pages" />
    <!--对应后缀名的页面,文件名是方法返回值-->
    <property name="suffix" value=".jsp" />
</bean>

<!--开启SpringMvc注解支持-->
<mvc:annotation-driven/>

mvc:annotation-driven说明

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

(8)启动服务器测试
点击链接,控制台输出 hello,springmvc

2.3 入门程序流程

1. 入门案例的执行流程

1. 当启动Tomcat服务器的时候,因为配置了load-on-startup标签,所以会创建DispatcherServlet对象,
就会加载springmvc.xml配置文件
2. 开启了注解扫描,那么HelloController对象就会被创建
3. 从index.jsp发送请求,请求会先到达DispatcherServlet核心控制器,根据配置@RequestMapping注解
找到执行的具体方法
4. 根据执行方法的返回值,再根据配置的视图解析器,去指定的目录下查找指定名称的JSP文件
5. Tomcat服务器渲染页面,做出响应

在这里插入图片描述

2. 图形

在这里插入图片描述

3. 入门案例中的组件分析

1. 前端控制器(DispatcherServlet)
2. 处理器映射器(HandlerMapping)
3. 处理器(Handler)
4. 处理器适配器(HandlAdapter)
5. 视图解析器(View Resolver)
6. 视图(View)

2.4 RequestMapping 注解

作用:用于建立请求 URL 和处理请求方法之间的对应关系。
源码:

//Target表明注解可以加到method之上,和type(类)上
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
}

类上加上注解,方法也加注解,访问路径是/user/hello,这样方便模块开发。

2.4.1 属性

value:用于指定请求的 URL。它和 path 属性的作用是一样的。
method:用于指定请求的方式。
params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的 key 和 value 必须和配置的一模一样。
headers:用于指定限制请求消息头的条件。

method测试:

使请求参数必须携带username属性

2.请求参数的绑定

2.1. 请求参数的绑定说明

2.1.1. 绑定机制

  1. 表单提交的数据都是k=v格式的 username=haha&password=123
  2. SpringMVC的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的
  3. 要求:提交表单的name和参数的名称是相同的
<a href="param/testParam?username=hehe&password=123"><h3>请求参数绑定</h3></a>
@Controller
@RequestMapping("/param")
public class ParamController {
    @RequestMapping("/testParam")
    public String testParam(String username,String password){
        System.out.println("方法执行.....");
        System.out.println("用户名:"+username);
        System.out.println("密码:"+password);
        return "success";
    }
}

输出

方法执行.....
用户名:hehe
密码:123

2.1.2. 支持的数据类型

  1. 基本数据类型和字符串类型
    1. 提交表单的name和参数的名称是相同的
    2. 区分大小写
  2. 实体类型(JavaBean)
    1. 提交表单的name和JavaBean中的属性名称需要一致
    2. 如果一个JavaBean类中包含其他的引用类型,那么表单的name属性需要编写成:对象.属性 例如:address.name
  3. 集合数据类型(List、map集合等)
    1. JSP页面编写方式:list[0].属性

Example1: 表单提交封装为javaBean对象

form表单

<form action="/param/saveAccount" method="post">
    姓名:<input type="text" name="username" ><br>
    密码:<input type="text" name="passowrd" ><br>
    金钱:<input type="text" name="money" ><br>
    <input type="submit" ><br>
</form>

javaBean

public class Account {
    private String username; //表单的name属性和javaBean的成员变量名一致
    private String password;
    private Double money;

java前端控制器方法

 @RequestMapping("/saveAccount")
    public String saveAccount(Account account){
        System.out.println(account);
        return "success";
    }

输出:

Account{username='Liang', password='null', money=39999.0}

Example2: 如果javaBean中包含引用类型变量表单中的name该如何设置?

如:Account中有User user成员属性。User类有username,age两个变量。
表单中的name可以写 user.username 或 user.age

2.1.3.解决post请求中文乱码的问题:

在web.xml中配CharacterEncodingFilter过滤器

 <!--配置过滤器解决中文乱码-->
  <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <!--配置编码为UTF-8-->
      <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>

Example3: JavaBean中存在集合

public class Account {
    private String username;
    private String password;
    private Double money;
    private List<User> lists;
    private Map<String,User> maps;

<form action="/param/saveAccount" method="post">
    姓名:<input type="text" name="username" ><br>
    密码:<input type="text" name="passowrd" ><br>
    金钱:<input type="text" name="money" ><br>
    用户姓名:<input type="text" name="lists[0].uname" ><br>
    用户年龄:<input type="text" name="lists[0].age" ><br>
    用户姓名:<input type="text" name="maps['one'].uname" ><br>
    用户年龄:<input type="text" name="maps['one'].age" ><br>
    <input type="submit" ><br>
</form>

java前端控制器方法

 @RequestMapping("/saveAccount")
    public String saveAccount(Account account){
        System.out.println(account);
        return "success";
    }

输出:

Account{username='张三', password='null', money=20000.0, lists=[User{uname='熊大', age='20'}], maps={one=User{uname='熊二', age='15'}}}

2.2.特殊情况

2.2.1 自定义类型转换器

表单提交的任何数据类型全部都是字符串类型,但是后台定义Integer类型,数据也可以封装上,说明Spring框架内部会默认进行数据类型转换。
(1)自定义类型转换异常演示

<form action="/param/saveUser" method="post">
    用户姓名:<input type="text" name="uname" ><br>
    用户年龄:<input type="text" name="age" ><br>
    出生日期:<input type="text" name="date" ><br>
    <input type="submit" ><br>
</form>

user对象

public class User {
    private String uname;
    private String age;
    private Date date;
    ···
}
@RequestMapping("/saveUser")
    public String saveUser(User user){
        System.out.println(user);
        return "success";
    }

日期格式为xxxx/xx/xx

日期格式为xxxx/xx/xx


springmvc可以正常转换,输出如下:

User{uname='梁', age='20', date=Sat Nov 11 00:00:00 CST 2000}

日期格式为xxxx-xx-xx


提交报错

(2) 解决方法自定义类型转换器

  1. 配置自定义类型转换类,实现Converter接口<S,T> 泛型S是传入的类型,T是转换后的类型
/**
 * 字符串转成日期类型
 * Create by LiangDong on 2019/8/4
 */
public class StringToDateConvert implements Converter<String,Date> {
    /**
     * @param source 传入的字符串
     * @return
     */
    public Date convert(String source) {
        if(source==null)
        {
            throw new RuntimeException("请输入数据在提交!");
        }
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        try {
            return df.parse(source);
        } catch (ParseException e) {
            throw new RuntimeException("数据转换错误!");
        }
    }
}
  1. 在springmvc.xml中配置注册自定义类型转换器
<bean id="conversionService2" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <!--注入自定义的数据类型转换器类-->
                <bean class="com.itheima.utils.StringToDateConvert"></bean>
            </set>
        </property>
    </bean>

    <!--开启SpringMvc注解支持 在添加自定义类型转换器的注解支持,因为默认不启动类型转换器-->
    <mvc:annotation-driven conversion-service="conversionService2"/>

3.获取Servlet原生态的API

在控制器中使用原生的ServletAPI对象
只需要在控制器的方法参数定义HttpServletRequest和HttpServletResponse对象

<a href="/param/testServlet">获取Servlet原生态的API</a>
@RequestMapping("/testServlet")
public String testServlet(HttpServletRequest request, HttpServletResponse response){
    System.out.println(request);
    System.out.println(response);
    System.out.println(request.getSession());
    System.out.println(request.getSession().getServletContext());
    return "success";
}

输出:

org.apache.catalina.connector.RequestFacade@3d9792ca
org.apache.catalina.connector.ResponseFacade@5f5d49a9
org.apache.catalina.session.StandardSessionFacade@168f1d2a
org.apache.catalina.core.ApplicationContextFacade@1084418a

3. 常用注解

3.1. RequestParam注解

  1. 作用:把请求中的指定名称的参数传递给控制器中的形参赋值
  2. 属性
    1. value:请求参数中的名称
    2. required:请求参数中是否必须提供此参数,默认值是true,必须提供
  3. 代码如下:

页面提交的参数名为name

<a href="anno/testRequstParam?name=哈哈">RequestParam</a>

控制器方法接受的参数名为username,属性会自动封装失败,输出为null

@Controller
@RequestMapping("/anno")
public class AnnoController {

    @RequestMapping("/testRequestParam")
    public String testRequestParam(String username){
        System.out.println(username);//null
        return "success";
    }
}

在参数地方添加@RequestParam注解,因为注解的required属性默认是true,因此指定传入的参数必须为name,此注解会将传递来的name属性赋值给形参username。

@RequestMapping("/testRequestParam")
    public String testRequestParam(@RequestParam("name") String username){
        System.out.println(username);//哈哈
        return "success";
    }

3.2. RequestBody注解

  1. 作用:用于获取请求体的内容(注意:get方法不可以)
  2. 属性
    1. required:是否必须有请求体,默认值是true
  3. 代码如下:
<form action="/anno/testRequestBody" method="post">
        姓名:<input type="text" name="username" ><br>
        年龄:<input type="text" name="age" ><br>
        <input type="submit" ><br>
</form>
/**
     * 获得请求体的内容
     * @param body
     * @return
     */
    @RequestMapping("/testRequestBody")
    public String testRequestBody(@RequestBody String body) {
        System.out.println(body);
        return "success";
    }

输出:

username=zhangsan&age=123

3.3. PathVariable注解

  1. 作用:获取绑定url中的占位符的值。例如:url中有/delete/{id},{id}就是占位符
  2. 属性
    1. value:指定url中的占位符名称
  3. Restful风格的URL
    1. 请求路径一样,可以根据不同的请求方式去执行后台的不同方法
    2. restful风格的URL优点
      1. 结构清晰
      2. 符合标准
      3. 易于理解
      4. 扩展方便

  4. 代码如下:
    <a href="anno/testPathVariable/1000">testPathVariable</a>
/**
     * PathVariable注解
     * @param id
     * @return
     */
    @RequestMapping("/testPathVariable/{sid}")
    public String testPathVariable(@PathVariable(name="sid") String id) {
        System.out.println(id);
        return "success";
    }

输出:

1000

3.4. RequestHeader注解

  1. 作用:获取指定请求头的值
  2. 属性
    1. value:请求头的名称
  3. 代码如下:
/**
     * RequestHeader注解
     * @param
     * @return
     */
    @RequestMapping("/testRequestHeader")
    public String testRequestHeader(@RequestHeader(value="accept") String header) {
        System.out.println(header);
        return "success";
    }

输出:

text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3

3.5. CookieValue注解

  1. 作用:用于获取指定cookie的名称的值
  2. 属性
    1. value:cookie的名称
  3. 代码:
@RequestMapping(path="/hello")
public String sayHello(@CookieValue(value="JSESSIONID") String cookieValue) {
System.out.println(cookieValue);//C0C07CF6D0724C8A45C6AEC328B95587
return "success";
}

3.6.ModelAttribute注解

  1. 作用
    1. 出现在方法上:表示当前方法会在控制器方法执行前线执行。
    2. 出现在参数上:获取指定的数据给参数赋值。
  2. 应用场景
    1. 当提交表单数据不是完整的实体数据时,保证没有提交的字段使用数据库原来的数据。
  3. 具体的代码:

form表单,属性不完整,缺少date属性

<form action="/anno/testModelAttribute" method="post">
        姓名:<input type="text" name="uname" ><br>
        年龄:<input type="text" name="age" ><br>
        <input type="submit" ><br>
    </form>

(1)第一种:注解出现在方法上进行数据的重新封装。

/**
 * ModelAttribute注解测试
 * @return
 */
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user){ //接受的user为showUser方法返回的
    System.out.println(user);
    return "success";
}

/**
 * 在控制器类的其他方法执行之前执行
 * @param uname
 * @param age
 * @return
 */
@ModelAttribute
public User showUser(String uname,String age){
    //(模拟)用户查询数据库,重新封装完整的
    User user = new User();
    user.setUname(uname);
    user.setAge(age);
    user.setDate(new Date());
    return user;
}

(2)第二种,注解出现在参数中。

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

@ModelAttribute
public void showUser(String uname, String age, Map<String,User> map){
    //模拟用户查询数据库,重新封装完整的
    User user = new User();
    user.setUname(uname);
    user.setAge(age);
    user.setDate(new Date());
    map.put("abc",user);
}

3.7. SessionAttributes注解

  1. 作用:用于多次执行控制器方法间的参数共享
  2. 属性
    1. value:指定存入属性的名称
  3. 代码如下:
@Controller
@RequestMapping("/anno")
@SessionAttributes(value = {"msg"})   //向session域中存入数据
public class AnnoController {
    /**
     * 使用model向request域中存储数据
     * @param model
     * @return
     */
    @RequestMapping("/testSessionAttribute")
    public String testSessionAttribute(Model model){
        System.out.println("testSessionAttribute");
        //底层向request域中存储数据
        model.addAttribute("msg","美美");
        return "success";
    }

    /**
     * 获取session中的数据
     * @param modelMap
     * @return
     */
    @RequestMapping("/getSessionAttribute")
    public String getSessionAttribute(ModelMap modelMap){
        System.out.println("testSessionAttribute");
        String msg = (String) modelMap.get("msg");
        System.out.println(msg);
        return "success";
    }

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

4.响应数据和结果视图

4.1. 控制器类方法返回值分类

4.1.1. 返回字符串

1. Controller方法返回字符串可以指定逻辑视图的名称,根据视图解析器为物理视图的地址。
@RequestMapping(value="/hello")
public String sayHello() {
    System.out.println("Hello SpringMVC!!");
    // 跳转到XX页面
    return "success";
}
4.1.1.1 具体的应用场景:修改用户,数据回显
@Controller
@RequestMapping("/user")
public class UserController {
    /**
    * 请求参数的绑定
    */
    @RequestMapping(value="/initUpdate")
    public String initUpdate(Model model) {
        // 模拟从数据库中查询的数据
        User user = new User();
        user.setUsername("张三");
        user.setPassword("123");
        user.setMoney(100d);
        user.setBirthday(new Date());
        model.addAttribute("user", user);//存放到request域中
        return "update";
    }
}

前台jsp页面从request域中取数据进行数据回显

<form action="user/initUpdate" method="post">
    姓名:<input type="text" name="username" value="${ user.username }"><br>
    密码:<input type="text" name="password" value="${ user.password }"><br>
    金额:<input type="text" name="money" value="${ user.money }"><br>
    <input type="submit" value="提交">
</form>

4.1.2 返回void类型

  1. 如果控制器的方法返回值编写成void,执行程序报404的异常,默认查找JSP页面没有找到。
    1. 默认会跳转到@RequestMapping(value="/initUpdate") initUpdate的页面。
  2. 可以使用请求转发或者重定向跳转到指定的页面
@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/testVoid")
    public void testVoid(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //转发到某页面
        //request.getRequestDispatcher("/pages/success.jsp").forward(request,response);

        //重定向到某页面
        //response.sendRedirect(request.getContextPath()+"/index.jsp");
        //直接返回数据
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().print("您好");

    }
}

4.1.3. 返回值是ModelAndView对象

  1. ModelAndView对象是Spring提供的一个对象,可以用来调整具体的JSP视图
  2. 具体的代码如下
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
    ModelAndView mv = new ModelAndView();
    User user = new User();
    user.setUsername("小风");
    user.setPassword("123");
    user.setAge(22);
    //底层也会将user对象存储到request域中
    mv.addObject("user",user);
    //会根据视图解析器对象进行查找页面
    mv.setViewName("success");//前台success.jsp页面中可以从request域中取数据
    return mv;
}

如果页面不是JSP,那么通过下面方法将modelandview转成JSON字符串返回

ModelAndView modelMap = new ModelAndView(new MappingJackson2JsonView());

4.2.SpringMVC框架提供的转发和重定向

使用关键字进行转发或重定向,那么就不能再使用视图解析器对象了。

4.2.1. forward请求转发

  1. controller方法返回String类型,想进行请求转发也可以编写成
  2. redirect重定向
   @RequestMapping("/testForwardOrRedirect")
   public String testForwardOrRedirect(){
       System.out.println("testForwardOrRedirect执行了...");
       //springmvc关键字转发或重定向
       //return "forward:/pages/success.jsp";
       return "redirect:/index.jsp";
   }

4.3. ResponseBody响应json数据

  1. DispatcherServlet会拦截到所有的资源,每一个静态资源都是单独请求的,导致一个问题就是静态资源(img、css、js)也会被拦截到,从而不能被使用。解决问题就是需要配置静态资源不进行拦截,在springmvc.xml配置文件添加如下配置
    1. mvc:resources标签配置不过滤
      1. location元素表示webapp目录下的包下的所有文件
      2. mapping元素表示以/static开头的所有请求路径,如/static/a 或者/static/a/b
<!--配置静态资源不过滤-->
    <mvc:resources location="/css/" mapping="/css/**"/> <!-- 样式 -->
    <mvc:resources location="/images/" mapping="/images/**"/> <!-- 图片 -->
    <mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript -->
  1. 使用@RequestBody获取请求体中的数据(包括但不限于json数据字符串)
<script type="text/javascript">
    $(function () {
       $("#btn").click(function () {
           //发送ajax请求
           $.ajax({
               url:"user/testAjax",
               contentType:"application/json;charset=utf-8",
               data:'{"username":"haha","password":"123","age":"22"}',
               dataType:"json",
               type:"post",
               success:function (data) {
                   //服务器端相应的json数据,进行解析。
               }
           })
       });
    });
</script>
@RequestMapping("/testAjax")
public void testAjax(@RequestBody String body){
    System.out.println(body);//{"username":"haha","password":"123","age":"22"}
}
  1. 使用@RequestBody注解把json的字符串转换成JavaBean的对象
    (1)前提:json字符串和JavaBean对象互相转换的过程中,需要使用jackson的jar包
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>

建议将maven中心仓库的源修改为阿里云的源。否则从仓库下载jar包慢的一批。
(2)使用@RequestBody注解把json的字符串转换成JavaBean的对象

@RequestMapping("/testAjax")
    public void testAjax(@RequestBody User user){
        System.out.println(user);//User{username='haha', password='123', age=22}
    }
  1. 使用@ResponseBody注解把JavaBean对象转换成json字符串,直接响应
    1. 要求方法需要返回JavaBean的对象
@RequestMapping("/testAjax")
    public @ResponseBody User testAjax(@RequestBody User user){
        //模拟数据库查询
        user.setUsername("heihei");
        user.setPassword("admin");
        return user;
    }
$(function () {
       $("#btn").click(function () {
           //发送ajax请求
           $.ajax({
               url:"user/testAjax",
               contentType:"application/json;charset=utf-8",
               data:'{"username":"haha","password":"123","age":"22"}',
               dataType:"json",
               type:"post",
               success:function (data) {
                   //服务器端相应的json数据,进行解析。
                   alert(data);
                   alert(data.username);
                   alert(data.password);
                   alert(data.age);

               }
           })
       });
    });

经过@ResponseBody的作用后直接转成了json格式
注意: 之前day01传递的参数都是如下这种类型
uname=hhhh&age=22&date=2000-11-11
到后台方法直接写个类型形参就能自动封装。
而今天的是json字符串,整个都是字符串:
{"username":"haha","password":"123","age":"22"}
不能不使用jackson的包就自动转换

5. Spring的文件上传

5.1. 文件上传的必要前提

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

5.2. 文件上传的原理分析

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

5.3.普通文件上传步骤

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

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
    </dependency>
    <dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

(2)写文件上传的JSP页面

<%--文件上传--%>
<form action="/user/testFileUpload" method="post" enctype="multipart/form-data">
    <input type="file" name="upload"/> <br>
    <input type="submit" value="上传">
</form>

(3)编写文件上传的控制器方法

/**
     * 文件上传的方法
     * @return
     */
    @RequestMapping("testFileUpload")
    public String testFileUpload(HttpServletRequest request) throws Exception {
        //先获取要上传的目录,gerRealPath是获取的tomcat服务器安装目录下的webapps/ROOT/目录
        String path = request.getSession().getServletContext().getRealPath("/uploads/");
        //创建File对象,一会向该对象下上传文件
        File file = new File(path);
        //如果文件路径不存在,则创建
        if(!file.exists())
        {
            file.mkdirs();
        }
        //创建磁盘文件项工厂
        DiskFileItemFactory dfi = new DiskFileItemFactory();
        ServletFileUpload sfu = new ServletFileUpload(dfi);
        //解析request对象
        List<FileItem> list = sfu.parseRequest(request);
        for (FileItem item : list) {
            //判断文件时普通字段还是上传的文件
            if(item.isFormField())
            {

            }else {
                //上传文件项
                //获取文件的名字
                String filename = item.getName();
                String uuid = UUID.randomUUID().toString().replace("-", "");
                //上传文件
                item.write(new File(file,uuid+filename));
                //删除临时文件
                item.delete();
            }
        }


        return "success";
    }

5.4.SpringMVC传统文件上传方式

5.4.1 Spring上传文件原理分析


SpringMVC框架提供了MultipartFile对象,该对象表示上传的文件,要求变量名称必须和表单file标签的
name属性名称相同。

5.4.2 传统方式上传文件

(1)编写上传文件表单

<h3>SpringMVC文件上传</h3>
<form action="/user/testFileUpload2" method="post" enctype="multipart/form-data">
    <input type="file" name="upload"/> <br>
    <input type="submit" value="上传">
</form>

(2)配置文件解析器对象

<!--配置文件解析器对象-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--可以配置最大上传文件的大小-->
        <property name="maxUploadSize" value="10485760"/>
    </bean>

(3)编写控制器类中上传文件方法

@RequestMapping("testFileUpload2")
public String testFileUpload2(HttpServletRequest request,MultipartFile upload) throws Exception {
    //先获取要上传的目录,gerRealPath是获取的tomcat服务器安装目录下的webapps/ROOT/目录
    String path = request.getSession().getServletContext().getRealPath("/uploads/");
    //创建File对象,一会向该对象下上传文件
    File file = new File(path);
    //如果文件路径不存在,则创建
    if(!file.exists())
    {
        file.mkdirs();
    }
    //上传文件项
    //获取文件的名字
    String filename = upload.getOriginalFilename();
    String uuid = UUID.randomUUID().toString().replace("-", "");
    //上传文件
    upload.transferTo(new File(file,uuid+filename));
    return "success";
}

5.5.SpringMvc跨服务器方式上传文件

5.5.1 分服务的目的

在实际开发中,我们会有很多处理不同功能的服务器。例如:
应用服务器:负责部署我们的应用
数据库服务器:运行我们的数据库
缓存和消息服务器:负责处理大并发访问的缓存和消息
文件服务器:负责存储用户上传文件的服务器。

5.5.2 步骤

  1. 搭建图片服务器
    1. 新建一个fileupload模块,配置tomcat本地服务,另起端口。
    2. 在webapp目录下创建uploads文件夹,作为文件服务器的上传文件目录。
  2. 实现SpringMVC跨服务器方式文件上传
    1. 导入开发需要的jar包
    <dependency>
        <groupId>com.sun.jersey</groupId>
        <artifactId>jersey-core</artifactId>
        <version>1.18.1</version>
        </dependency>
        <dependency>
        <groupId>com.sun.jersey</groupId>
        <artifactId>jersey-client</artifactId>
        <version>1.18.1</version>
    </dependency>
    
    1. 编写文件上传的JSP页面
    <h3>SpringMVC跨服务器文件上传</h3>
    <form action="/user/testFileUpload3" method="post" enctype="multipart/form-data">
        <input type="file" name="upload"/> <br>
        <input type="submit" value="上传">
    </form>
    
    1. 编写控制器
    /**
     * SpringMvc跨服务器文件上传的方法
     * @return
     */
    @RequestMapping("testFileUpload3")
    public String testFileUpload3(MultipartFile upload) throws Exception {
        //定义服务器的路径
        String path = "http://localhost:8081/uploads/";
    
        //上传文件项
        //获取文件的名字
        String filename = upload.getOriginalFilename();
        String uuid = UUID.randomUUID().toString().replace("-", "");
    
        //创建客户端对象
        Client client = Client.create();
        //和服务器进行链接获取资源对象
        WebResource webResource = client.resource(path);
        //上传文件
        webResource.put(upload);
        return "success";
    }
    

报403Fobbiden错误,原因是跨服务器tomcat默认是只读的。
在tomcat的web.xml中配置readonly属性为false
报409错误,一般是目录不存在,或路径不正确。

6.Spring的异常

6.1异常处理的思路

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

6.2 实现步骤

(1)编写自定义异常类(做提示信息)

/**
 * 自定义异常类
 * Create by LiangDong on 2019/8/6
 */
public class SysException extends Exception {
    //存储提示信息
    private String message;

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

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

    public void setMessage(String message) {
        this.message = message;
    }
}

(2)修改控制器中的方法异常处理

/**
 * 异常处理测试
 * @return
 * @throws SysException
 */
@RequestMapping("testException")
public String testException() throws SysException{
    System.out.println("testException方法执行了...");
    //捕获service,dao抛出的异常
    try {
        int a= 10/0;
    } catch (Exception e) {
        e.printStackTrace();
        throw new SysException("查询所有用户出错...");
    }
    return "success";
}

(3)编写异常处理器

/**
 * 自定义异常处理器
 * Create by LiangDong on 2019/8/6
 */
public class SysExceptionResolver implements HandlerExceptionResolver {
    /**
     *
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     * @param e 为控制器抛出的异常
     * @return
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        SysException s = null;
        if(e instanceof SysException)
        {
            s = (SysException) e;
        }else {
            s = new SysException("系统正在维护,请等待...");
        }
        ModelAndView mv = new ModelAndView();
        mv.addObject("errorMsg",s.getMessage());
        mv.setViewName("error");
        return mv;
    }
}

(4)配置异常处理器(跳转提示页面)

<!--配置异常解析器-->
<bean id="sysExceptionResolver" class="cn.itcast.exception.SysExceptionResolver"/>

7.Spring的拦截器

7.1 拦截器的作用

Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
用户可以自己定义一些拦截器来实现特定的功能。
谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺
序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
说到这里,可能大家脑海中有了一个疑问,这不是我们之前学的过滤器吗?是的它和过滤器是有几分相似,但
是也有区别,接下来我们就来说说他们的区别:
过滤器是 servlet 规范中的一部分,任何 java web 工程都可以使用。
拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
过滤器在 url-pattern 中配置了`/*` 之后,可以对所有要访问的资源拦截。
拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦
截的。
它也是 AOP 思想的具体应用。
我们要想自定义拦截器, 要求必须实现:HandlerInterceptor  接口。

7.2自定义拦截器的步骤

(1)编写拦截器类实现HandlerInterceptor接口

/**
 * 自定义拦截器
 * Create by LiangDong on 2019/8/6
 */
public class MyInterceptor1 implements HandlerInterceptor{
    /**
     * 控制器方法执行之前执行的方法。
     * return true 表示放行,执行控制器类的方法
     * return false 表示不放行,可以在之前配置跳转到其他页面。如权限不够。
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor1执行了....");
        return true;
    }
}

(2)配置拦截器

<!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--拦截的方法-->
            <mvc:mapping path="/user/**"/>
            <!--不拦截的方法
            <mvc:exclude-mapping path=""/>-->
            <!--配置自定义的拦截器对象-->
            <bean class="cn.itcast.Interceptor.MyInterceptor1"/>
        </mvc:interceptor>
    </mvc:interceptors>

(3)控制器类方法测试

/**
     * 拦截器测试
     * @return
     */
    @RequestMapping("testInterceptor1")
    public String testInterceptor1(){
        System.out.println("testInterceptor1执行了....");
        return "success";
    }

(4)页面测试

<% System.out.printf("success.jsp执行了...");%>

输出:

MyInterceptor1执行了....
testInterceptor1执行了....
success.jsp执行了...

7.3 HandlerInterceptor接口中的方法

  1. preHandle方法是controller方法执行前拦截的方法
    1. 可以使用request或者response跳转到指定的页面
    2. return true放行,执行下一个拦截器,如果没有拦截器,执行controller中的方法。
    3. return false不放行,不会执行controller中的方法。
  2. postHandle是controller方法执行后执行的方法,在JSP视图执行前。
    1. 可以使用request或者response跳转到指定的页面
    2. 如果指定了跳转的页面,那么controller方法跳转的页面将不会显示。
  3. afterCompletion方法是在JSP执行后执行
    1. request或者response不能再跳转页面了

7.4 配置多个拦截器

<!-- 配置拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
    <!-- 哪些方法进行拦截 -->
        <mvc:mapping path="/user/*"/>
        <!-- 哪些方法不进行拦截
        <mvc:exclude-mapping path=""/>
        -->
        <!-- 注册拦截器对象 -->
        <bean class="cn.itcast.demo1.MyInterceptor1"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <!-- 哪些方法进行拦截 -->
        <mvc:mapping path="/**"/>
        <!-- 注册拦截器对象 -->
        <bean class="cn.itcast.demo1.MyInterceptor2"/>
    </mvc:interceptor>
</mvc:interceptors>

相关文章

开发过程中是不可避免地会出现各种异常情况的,例如网络连接...
说明:使用注解方式实现AOP切面。 什么是AOP? 面向切面编程...
Spring MVC中的拦截器是一种可以在请求处理过程中对请求进行...
在 JavaWeb 中,共享域指的是在 Servlet 中存储数据,以便在...
文件上传 说明: 使用maven构建web工程。 使用Thymeleaf技术...
创建初始化类,替换web.xml 在Servlet3.0环境中,Web容器(To...