【Java学习路线之JavaWeb】Spring MVC框架入门教程_springmvc先写哪个模块

写在最后

为了这次面试,也收集了很多的面试题!

以下是部分面试题截图

Java程序员秋招三面蚂蚁金服,我总结了所有面试题,也不过如此

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

springmvcDemo2 应用目录结构如下。

springmvcDemo2目录结构
springmvcDemo2目录结构

web.xml 代码如下。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    <display-name>springMVC</display-name>
    <!-- 部署 DispatcherServlet -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/springmvc-servlet.xml</param-value>
        </init-param>
        <!-- 表示容器再启动时立即加载servlet -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- 处理所有URL -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

springmvc-servlet.xml 配置文件代码如下。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/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">
    <context:component-scan
        base-package="net.biancheng.controller" />
    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/" />
        <!--后缀 -->
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

创建 User 实体类,代码如下。如前面所说,使用 Controller 注解的一个优点在于一个控制类可以包含多个请求处理方法。创建 UserController,代码如下。

package net.biancheng.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import net.biancheng.po.User;
@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/login")
    public String getLogin(Model model) {
        User us = new User();
        us.setName("编程帮");
        model.addAttribute("user", us);
        return "login";
    }
    @RequestMapping("/register")
    public String getRegister() {
        return "register";
    }
}

index.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>Insert title here</title>
</head>
<body>
    未注册的用户,请
    <a href="${pageContext.request.contextPath }/user/register"> 注册</a>!
    <br /> 已注册的用户,去
    <a href="${pageContext.request.contextPath }/user/login"> 登录</a>!
</body>
</html>

login.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>Insert title here</title>
</head>
<body>
    登录页面! 欢迎 ${user.name} 登录
</body>
</html>

register.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>Insert title here</title>
</head>
<body>
    注册页面!
</body>
</html>

运行结果如下。

index.jsp页面
index.jsp页面

register.jsp页面
register.jsp页面

login.jsp页面
login.jsp页面

在上图所示的页面中,当用户单击“注册”超链接时,控制器会将该请求转发给 UserController 的 getLogin 方法处理,处理后跳转到 /WEB-INF/jsp 下的 register.jsp 视图。同理,当单击“登录”超链接时,控制器处理后转到 /WEB-INF/jsp下的 login.jsp 视图。

Spring MVC传递参数

Spring MVC Controller 接收请求参数的方式有很多种,有的适合 get 请求方式,有的适合 post 请求方式,有的两者都适合。主要有以下几种方式:

  • 通过实体 Bean 接收请求参数
  • 通过处理方法的形参接收请求参数
  • 通过 HttpServletRequest 接收请求参数
  • 通过 @PathVariable 接收 URL 中的请求参数
  • 通过 @RequestParam 接收请求参数
  • 通过 @ModelAttribute 接收请求参数

下面分别介绍这些方式,读者可以根据实际情况选择合适的接收方式。

通过实体Bean接收请求参数

实体 Bean 接收请求参数适用于 get 和 post 提交请求方式。需要注意,Bean 的属性名称必须与请求参数名称相同。示例代码如下。

@RequestMapping("/login")
public String login(User user, Model model) {
    if ("bianchengbang".equals(user.getName())
            && "123456".equals(user.getPwd())) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

通过处理方法的形参接收请求参数

通过处理方法的形参接收请求参数就是直接把表单参数写在控制器类相应方法的形参中,即形参名称与请求参数名称完全相同。该接收参数方式适用于 get 和 post 提交请求方式。示例代码如下:

@RequestMapping("/login")
public String login(String name, String pwd, Model model) {
    if ("bianchengbang".equals(user.getName())
            && "123456".equals(user.getPwd())) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

通过HttpServletRequest接收请求参数

通过 HttpServletRequest 接收请求参数适用于 get 和 post 提交请求方式,示例代码如下:

@RequestMapping("/login")
public String login(HttpServletRequest request, Model model) {
    String name = request.getParameter("name");
    String pwd = request.getParameter("pwd");
   
    if ("bianchengbang".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

通过@PathVariable接收URL中的请求参数

通过 @PathVariable 获取 URL 中的参数,示例代码如下。

@RequestMapping("/login/{name}/{pwd}")
public String login(@PathVariable String name, @PathVariable String pwd, Model model) {
   
    if ("bianchengbang".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

在访问“http://localhost:8080/springMVCDemo02/user/register/bianchengbang/123456”路径时,上述代码会自动将 URL 中的模板变量 {name} 和 {pwd} 绑定到通过 @PathVariable 注解的同名参数上,即 name=bianchengbang、pwd=123456。

通过@RequestParam接收请求参数

在方法入参处使用 @RequestParam 注解指定其对应的请求参数。@RequestParam 有以下三个参数:

  • value:参数名
  • required:是否必须,默认为 true,表示请求中必须包含对应的参数名,若不存在将抛出异常
  • defaultValue:参数默认值

通过 @RequestParam 接收请求参数适用于 get 和 post 提交请求方式,示例代码如下。

@RequestMapping("/login")
public String login(@RequestParam String name, @RequestParam String pwd, Model model) {
   
    if ("bianchengbang".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

该方式与“通过处理方法的形参接收请求参数”部分的区别如下:当请求参数与接收参数名不一致时,“通过处理方法的形参接收请求参数”不会报 404 错误,而“通过 @RequestParam 接收请求参数”会报 404 错误。

通过@ModelAttribute接收请求参数

@ModelAttribute 注解用于将多个请求参数封装到一个实体对象中,从而简化数据绑定流程,而且自动暴露为模型数据,在视图页面展示时使用。

而“通过实体 Bean 接收请求参数”中只是将多个请求参数封装到一个实体对象,并不能暴露为模型数据(需要使用 model.addAttribute 语句才能暴露为模型数据,数据绑定与模型数据展示后面教程中会讲解)。

通过 @ModelAttribute 注解接收请求参数适用于 get 和 post 提交请求方式,示例代码如下。

@RequestMapping("/login")
public String login(@ModelAttribute("user") User user, Model model) {
   
    if ("bianchengbang".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

Spring MVC重定向和转发

Spring MVC 请求方式分为转发、重定向 2 种,分别使用 forward 和 redirect 关键字在 controller 层进行处理。

重定向是将用户从当前处理请求定向到另一个视图(例如 JSP)或处理请求,以前的请求(request)中存放的信息全部失效,并进入一个新的 request 作用域;转发是将用户对当前处理的请求转发给另一个视图或处理请求,以前的 request 中存放的信息不会失效。

转发是服务器行为,重定向是客户端行为。

1)转发过程

客户浏览器发送 http 请求,Web 服务器接受此请求,调用内部的一个方法在容器内部完成请求处理和转发动作,将目标资源发送给客户;在这里转发的路径必须是同一个 Web 容器下的 URL,其不能转向到其他的 Web 路径上,中间传递的是自己的容器内的 request。

在客户浏览器的地址栏中显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。

2)重定向过程

客户浏览器发送 http 请求,Web 服务器接受后发送 302 状态码响应及对应新的 location 给客户浏览器,客户浏览器发现是 302 响应,则自动再发送一个新的 http 请求,请求 URL 是新的 location 地址,服务器根据此请求寻找资源并发送给客户。

在这里 location 可以重定向到任意 URL,既然是浏览器重新发出了请求,那么就没有什么 request 传递的概念了。在客户浏览器的地址栏中显示的是其重定向的路径,客户可以观察到地址的变化。重定向行为是浏览器做了至少两次的访问请求。

在 Spring MVC 框架中,控制器类中处理方法的 return 语句默认就是转发实现,只不过实现的是转发到视图。示例代码如下:

@RequestMapping("/register")
public String register() {
    return "register";  //转发到register.jsp
}

在 Spring MVC 框架中,重定向与转发的示例代码如下:

package net.biancheng.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/index")
public class IndexController {
    @RequestMapping("/login")
    public String login() {
        //转发到一个请求方法(同一个控制器类可以省略/index/)
        return "forward:/index/isLogin";
    }
    @RequestMapping("/isLogin")
    public String isLogin() {
        //重定向到一个请求方法
        return "redirect:/index/isRegister";
    }
    @RequestMapping("/isRegister")
    public String isRegister() {
        //转发到一个视图
        return "register";
    }
}

在 Spring MVC 框架中,不管是重定向或转发,都需要符合视图解析器的配置,如果直接转发到一个不需要 DispatcherServlet 的资源,例如:

return “forward:/html/my.html”;

则需要使用 mvc:resources 配置:

<mvc:resources location=“/html/” mapping=“/html/**” />

Spring MVC @Autowired和@Service注解

将依赖注入到 Spring MVC 控制器时需要用到 @Autowired 和 @Service 注解。

@Autowired 注解属于 org.springframework.beans.factory. annotation 包,可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。

@Service 注解属于 org.springframework.stereotype 包,会将标注类自动注册到 Spring 容器中。

在配置文件中需要添加 元素来扫描依赖基本包。

<context:component-scan base-package=“net.biancheng.service”/>

示例

下面新建 Web 应用 springmvcDemo3 进一步说明 Spring MVC 如何应用依赖注入。应用目录结构如下。

springmvcDemo3的目录结构

User 实体类如下。

package net.biancheng.po;
public class User {
    private String name;
    private String pwd;
    /*省略setter和getter方法*/
}

新建 net.biancheng.service 包,创建 UserService 接口,代码如下。

package net.biancheng.service;
import net.biancheng.po.User;
public interface UserService {
    boolean login(User user);
    boolean register(User user);
}

创建 UserServiceImpl 类,实现 UserService 接口,代码如下。

package net.biancheng.service;
import org.springframework.stereotype.Service;
import net.biancheng.po.User;
@Service
public class UserServiceImpl implements UserService {
    @Override
    public boolean login(User user) {
        if ("bianchengbang".equals(user.getName()) && "123456".equals(user.getPwd())) {
            return true;
        }
        return false;
    }
    @Override
    public boolean register(User user) {
        if ("bianchengbang".equals(user.getName()) && "123456".equals(user.getPwd())) {
            return true;
        }
        return false;
    }
}

注意:为了使类能被 Spring 扫描到,必须为其标注 @Service。

新建 net.biancheng.controller 包,创建 UserController 类,代码如下。

package net.biancheng.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import net.biancheng.po.User;
import net.biancheng.service.UserService;
@Controller
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/login")
    public String getLogin(Model model) {
        User us = new User();
        us.setName("bianchengbang");
        userService.login(us);
        model.addAttribute("user", us);
        return "login";
    }
    @RequestMapping("/register")
    public String getRegister(Model model) {
        User us = new User();
        us.setName("bianchengbang");
        userService.login(us);
        model.addAttribute("user", us);
        return "register";
    }
}

在 UserService 上添加 @Autowired 注解会使 UserService 的一个实例被注入到 UserController 实例中。

springmvc-servlet.xml 代码如下。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/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">
    <context:component-scan
        base-package="net.biancheng" />
    <mvc:annotation-driven />
    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/" />
        <!--后缀 -->
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

web.xml 代码如下。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    <display-name>springMVC</display-name>
    <!-- 部署 DispatcherServlet -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/springmvc-servlet.xml</param-value>
        </init-param>
        <!-- 表示容器再启动时立即加载servlet -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- 处理所有URL -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

index.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>Insert title here</title>
</head>
<body>
    未注册的用户,请
    <a href="${pageContext.request.contextPath }/user/register"> 注册</a>!
    <br /> 已注册的用户,去
    <a href="${pageContext.request.contextPath }/user/login"> 登录</a>!
</body>
</html>

login.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>Insert title here</title>
</head>
<body>
    登录页面! 欢迎 ${user.name} 登录
</body>
</html>

register.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>Insert title here</title>
</head>
<body>
    注册页面!
    注册账号成功,用户名为: ${user.name }
</body>
</html>

运行结果如下。

index.jsp页面
index.jsp页面

register.jsp页面
register.jsp页面

login.jsp页面
login.jsp页面

Spring MVC类型转换器(Converter)

Spring MVC 框架的 Converter<S,T> 是一个可以将一种数据类型转换成另一种数据类型的接口,这里 S 表示源类型,T 表示目标类型。开发者在实际应用中使用框架内置的类型转换器基本上就够了,但有时需要编写具有特定功能的类型转换器。

例如,用户输入的日期可能有许多种形式,如“December 25,2014”“12/25/2014”和“2014-12-25”,这些都表示同一个日期。默认情况下,Spring 会期待用户输入的日期样式与当前语言区域的日期样式相同。例如,对于美国的用户而言,就是月/日/年的格式。如果希望 Spring 在将输入的日期字符串绑定到 LocalDate 时,使用不同的日期样式,则需要编写一个 Converter,才能将字符串转换成日期。

java.time.LocalDate 类是 Java 8 的一个新类型,用来替代 java.util.Date。还需使用新的 Date/Time API 来替换旧有的 Date 和 Calendar 类。

内置的类型转换器

在 Spring MVC 框架中,对于常用的数据类型,开发者无须创建自己的类型转换器,因为 Spring MVC 框架有许多内置的类型转换器用于完成常用的类型转换。Spring MVC 框架提供的内置类型转换包括以下几种类型。

1)标量转换器
名称作用
StringToBooleanConverterString 到 boolean 类型转换
ObjectToStringConverterObject 到 String 转换,调用 toString 方法转换
StringToNumberConverterFactoryString 到数字转换(例如 Integer、Long 等)
NumberToNumberConverterFactory数字子类型(基本类型)到数字类型(包装类型)转换
StringToCharacterConverterString 到 Character 转换,取字符串中的第一个字符
NumberToCharacterConverter数字子类型到 Character 转换
CharacterToNumberFactoryCharacter 到数字子类型转换
StringToEnumConverterFactoryString 到枚举类型转换,通过 Enum.valueOf 将字符串转换为需要的枚举类型
EnumToStringConverter枚举类型到 String 转换,返回枚举对象的 name 值
StringToLocaleConverterString 到 java.util.Locale 转换
PropertiesToStringConverterjava.util.Properties 到 String 转换,默认通过 ISO-8859-1 解码
StringToPropertiesConverterString 到 java.util.Properties 转换,默认使用 ISO-8859-1 编码
2)集合、数组相关转换器
名称作用
ArrayToCollectionConverter任意数组到任意集合(List、Set)转换
CollectionToArrayConverter任意集合到任意数组转换
ArrayToArrayConverter任意数组到任意数组转换
CollectionToCollectionConverter集合之间的类型转换
MapToMapConverterMap之间的类型转换
ArrayToStringConverter任意数组到 String 转换
StringToArrayConverter字符串到数组的转换,默认通过“,”分割,且去除字符串两边的空格(trim)
ArrayToObjectConverter任意数组到 Object 的转换,如果目标类型和源类型兼容,直接返回源对象;否则返回数组的第一个元素并进行类型转换
ObjectToArrayConverterObject 到单元素数组转换
CollectionToStringConverter任意集合(List、Set)到 String 转换
StringToCollectionConverterString 到集合(List、Set)转换,默认通过“,”分割,且去除字符串两边的空格(trim)
CollectionToObjectConverter任意集合到任意 Object 的转换,如果目标类型和源类型兼容,直接返回源对象;否则返回集合的第一个元素并进行类型转换
ObjectToCollectionConverterObject 到单元素集合的类型转换

类型转换是在视图与控制器相互传递数据时发生的。Spring MVC 框架对于基本类型(例如 int、long、float、double、boolean 以及 char 等)已经做好了基本类型转换。

注意:在使用内置类型转换器时,请求参数输入值与接收参数类型要兼容,否则会报 400 错误。请求参数类型与接收参数类型不兼容问题需要学习输入校验后才可解决。

自定义类型转换器

当 Spring MVC 框架内置的类型转换器不能满足需求时,开发者可以开发自己的类型转换器。

例如需要用户在页面表单中输入信息来创建商品信息。当输入“bianchengbang,18,1.85”时表示在程序中自动创建一个 new User,并将“bianchengbang”值自动赋给 name 属性,将“18”值自动赋给 age 属性,将“1.85”值自动赋给 height 属性。

如果想实现上述应用,需要做以下 5 件事:

  • 创建实体类。
  • 创建控制器类。
  • 创建自定义类型转换器类。
  • 注册类型转换器。
  • 创建相关视图。

示例

本节示例基于《@Controller和@RequestMapping注解》一节创建的 springmvcDemo2 应用。

1. 创建实体类

在 net.biancheng.po 包下创建 User 实体类,代码如下。

package net.biancheng.po;
public class User {
    private String name;
    private Integer age;
    private Double height;
    /**省略setter和getter方法*/
}

2. 创建控制器类

在 net.biancheng.controller 包下创建 UserController 控制器,代码如下。

package net.biancheng.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import net.biancheng.po.User;
@Controller
public class UserController {
    @RequestMapping("/addUser")
    public String addUser() {
        return "addUser";
    }
}

创建 ConverterController 控制器,代码如下。

package net.biancheng.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import net.biancheng.po.User;
@Controller
public class ConverterController {
    @RequestMapping("/converter")
    public String myConverter(@RequestParam("user") User user, Model model) {
        model.addAttribute("user", user);
        return "showUser";
    }
}

3. 创建自定义类型转换器

创建 net.biancheng.converter,在该包下创建自定义类型转换器 UserConverter,代码如下。

package net.biancheng.converter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import net.biancheng.po.User;
@Component
public class UserConverter implements Converter<String, User> {
    @Override
    public User convert(String source) {
        // 创建User实例
        User user = new User();
        // 以“,”分隔
        String stringvalues[] = source.split(",");
        if (stringvalues != null && stringvalues.length == 3) {
            // 为user实例赋值
            user.setName(stringvalues[0]);
            user.setAge(Integer.parseInt(stringvalues[1]));
            user.setHeight(Double.parseDouble(stringvalues[2]));
            return user;
        } else {
            throw new IllegalArgumentException(String.format("类型转换失败, 需要格式'编程帮, 18,1.85',但格式是[% s ] ", source));
        }
    }
}

4. 配置转换器

在 springmvc-servlet.xml 文件中添加以下代码。

<mvc:annotation-driven conversion-service="conversionService" />
<!--注册类型转换器UserConverter -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="net.biancheng.converter.UserConverter" />
        </list>
    </property>
</bean>

5. 创建相关视图

创建添加用户页面 addUser.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>添加用户</title>
</head>
<body>
    <form action="${pageContext.request.contextPath}/converter"
        method="post">
        请输入用户信息(格式为编程帮, 18,1.85):
        <input type="text" name="user" />
        <br>
        <input type="submit" value="提交" />
    </form>
</body>
</html>

创建显示用户页面 showUser.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>文件上传</title>
</head>
<body>
    您创建的用户信息如下:
    <br/>
    <!-- 使用EL表达式取出model中的user信息 -->
    用户名:${user.name } <br/>
    年龄:${user.age } <br/>
    身高:${user.height }
</body>
</html>

6. 测试

访问地址:http://localhost:8080/springmvcDemo2/addUser,运行结果如下图所示。

addUser.jsp
addUser.jsp

showUser.jsp
showUser.jsp

Spring MVC数据格式化(Formatter)

Spring MVC 框架的 Formatter 与 Converter<S, T> 一样,也是一个可以将一种数据类型转换成另一种数据类型的接口。不同的是,Formatter 的源类型必须是 String 类型,而 Converter 的源类型可以是任意数据类型。Formatter 更适合 Web 层,而 Converter 可以在任意层中。所以对于需要转换表单中的用户输入的情况,应该选择 Formatter,而不是 Converter。

在 Web 应用中由 HTTP 发送的请求数据到控制器中都是以 String 类型获取,因此在 Web 应用中选择 Formatter 比选择 Converter<S, T> 更加合理。

内置的格式化转换器

Spring MVC 提供了几个内置的格式化转换器,具体如下。

  • NumberFormatter:实现 Number 与 String 之间的解析与格式化。
  • CurrencyFormatter:实现 Number 与 String 之间的解析与格式化(带货币符号)。
  • PercentFormatter:实现 Number 与 String 之间的解析与格式化(带百分数符号)。
  • DateFormatter:实现 Date 与 String 之间的解析与格式化。

自定义格式化转换器

自定义格式化转换器就是编写一个实现 org.springframework.format.Formatter 接口的 Java 类。该接口声明如下。

public interface Formatter<T>

这里的 T 表示由字符串转换的目标数据类型。该接口有 parse 和 print 两个接口方法,自定义格式化转换器类必须覆盖它们。

public T parse(String s, java.util.Locale locale)
public String print(T object, java.util.Locale locale)

parse 方法的功能是利用指定的 Locale 将一个 String 类型转换成目标类型,print 方法与之相反,用于返回目标对象的字符串表示。

示例

下面通过具体应用讲解自定义格式化转换器的用法,本节示例基于《@Controller和@RequestMapping注解》一节中的 springmvcDemo2 程序。

1. 创建实体类

创建 net.biancheng.po 包,并在该包中创建 User 实体类,代码如下。

package net.biancheng.po;
import java.util.Date;
public class User {
    private String name;
    private Integer age;
    private Double height;
    private Date createDate;
    /**省略setter和getter方法*/
}

2. 创建控制器类

创建 net.biancheng.controller 包,并在该包中创建 FormatterController 控制器类,代码如下。

package net.biancheng.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import net.biancheng.po.User;
@Controller
public class FormatterController {
    @RequestMapping("/formatter")
    public String myFormatter(User us, Model model) {
        model.addAttribute("user", us);
        return "showUser";
    }
}

3. 创建自定义格式化转换器类

创建 net.biancheng.formatter 包,并在该包中创建 MyFormatter 的自定义格式化转换器类,代码如下。

package net.biancheng.formatter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import org.springframework.format.Formatter;
import org.springframework.stereotype.Component;
@Component
public class MyFormatter implements Formatter<Date> {
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    public String print(Date object, Locale arg1) {
        return dateFormat.format(object);
    }
    public Date parse(String source, Locale arg1) throws ParseException {
        return dateFormat.parse(source); // Formatter只能对字符串转换
    }
}

4. 注册格式化转换器

在 springmvc-servlet.xml 配置文件中注册格式化转换器,具体代码如下:

<!--注册MyFormatter -->
<bean id="conversionService"
    class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="formatters">
        <set>
            <bean class="net.biancheng.formatter.MyFormatter" />
        </set>
    </property>
</bean>
<mvc:annotation-driven conversion-service="conversionService" />

5. 创建相关视图

创建添加用户页面 addUser.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>添加用户</title>
</head>
<body>
    <form action="${pageContext.request.contextPath}/formatter" method="post">
        用户名:<input type="text" name="name" />
        <br>
        年龄:<input type="text" name="age" />
        <br>
        身高:<input type="text" name="height" />
        <br>
        创建日期:<input type="text" name="createDate" />
        <br>
        <input type="submit" value="提交" />
    </form>
</body>
</html>

创建信息显示页面 showUser.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>用户信息</title>
</head>
<body>
    您创建的用户信息如下:
    <br />
    <!-- 使用EL表达式取出model中的user信息 -->
    用户名:${user.name }
    <br />
    年龄:${user.age }
    <br />
    身高:${user.height }
    <br />
    创建日期:${user.createDate }
</body>
</html>

6. 测试运行

访问地址:http://localhost:8080/springmvcDemo2/addUser。

addUser.jsp
addUser.jsp

showUser.jsp
showUser.jsp

Spring MVC JSON数据交互

Spring MVC 在数据绑定的过程中需要对传递数据的格式和类型进行转换,它既可以转换 String 等类型的数据,也可以转换 JSON 等其他类型的数据。本节将针对 Spring MVC 中 JSON 类型的数据交互进行讲解。

JSON 概述

JSON(JavaScript Object Notation, JS 对象标记)是一种轻量级的数据交换格式。与 XML 一样,JSON 也是基于纯文本的数据格式。它有对象结构和数组结构两种数据结构。

1)对象结构

对象结构以{开始、以}结束,中间部分由 0 个或多个以英文,分隔的 key/value 对构成,key 和 value 之间以英文:分隔。对象结构的语法结构如下:

{
key1:value1,
key2:value2,

}

其中,key 必须为 String 类型,value 可以是 String、Number、Object、Array 等数据类型。例如,一个 person 对象包含姓名、密码、年龄等信息,使用 JSON 的表示形式如下:

{
“pname”:“张三”,
“password”:“123456”,
“page”:40
}

2)数组结构

数组结构以[开始、以]结束,中间部分由 0 个或多个以英文,分隔的值的列表组成。数组结构的语法结构如下:

{
value1,
value2,

}

上述两种(对象、数组)数据结构也可以分别组合构成更加复杂的数据结构。例如,一个 student 对象包含 sno、sname、hobby 和 college 对象,其 JSON 的表示形式如下:

{
“sno”:“201802228888”,
“sname”:“张三”,
“hobby”:[“篮球”,“足球”],
“college”:{
“cname”:“清华大学”,
“city”:“北京”
}
}

JSON 数据转换

为实现浏览器与控制器类之间的 JSON 数据交互,Spring MVC 提供了 MappingJackson2HttpMessageConverter 实现类默认处理 JSON 格式请求响应。该实现类利用 Jackson 开源包读写 JSON 数据,将 Java 对象转换为 JSON 对象和 XML 文档,同时也可以将 JSON 对象和 XML 文档转换为 Java 对象。

在使用注解开发时需要用到两个重要的 JSON 格式转换注解,分别是 @RequestBody 和 @ResponseBody。

  • @RequestBody:用于将请求体中的数据绑定到方法的形参中,该注解应用在方法的形参上。
  • @ResponseBody:用于直接返回 return 对象,该注解应用在方法上。

需要注意的是,在该处理方法上,除了通过 @RequestMapping 指定请求的 URL,还有一个 @ResponseBody 注解。该注解的作用是将标注该注解的处理方法的返回结果直接写入 HTTP Response Body(Response 对象的 body 数据区)中。一般情况下,@ResponseBody 都会在异步获取数据时使用,被其标注的处理方法返回的数据都将输出到响应流中,客户端获取并显示数据。

各个JSON技术比较

早期 JSON 的组装和解析都是通过手动编写代码来实现的,这种方式效率不高,所以后来有许多的关于组装和解析 JSON 格式信息的工具类出现,如 json-lib、Jackson、Gson 和 FastJson 等,可以解决 JSON 交互的开发效率。

1)json-lib

json-lib 最早也是应用广泛的 JSON 解析工具,缺点是依赖很多的第三方包,如 commons-beanutils.jar、commons-collections-3.2.jar、commons-lang-2.6.jar、commons-logging-1.1.1.jar、ezmorph-1.0.6.jar 等。

对于复杂类型的转换,json-lib 在将 JSON 转换成 Bean 时还有缺陷,比如一个类里包含另一个类的 List 或者 Map 集合,json-lib 从 JSON 到 Bean 的转换就会出现问题。

所以 json-lib 在功能和性能上面都不能满足现在互联网化的需求。

2)开源的Jackson

开源的 Jackson 是 Spring MVC 内置的 JSON 转换工具。相比 json-lib 框架,Jackson 所依赖 jar 文件较少,简单易用并且性能也要相对高些。并且 Jackson 社区相对比较活跃,更新速度也比较快。

但是 Jackson 对于复杂类型的 JSON 转换 Bean 会出现问题,一些集合 Map、List 的转换出现问题。而 Jackson 对于复杂类型的 Bean 转换 JSON,转换的 JSON 格式不是标准的 JSON 格式。

3)Google的Gson

Gson 是目前功能最全的 JSON 解析神器,Gson 当初是应 Google 公司内部需求由 Google 自行研发。自从在 2008 年 5 月公开发布第一版后,Gson 就已经被许多公司或用户应用。

Gson 主要提供了 toJson 与 fromJson 两个转换函数,不需要依赖其它的 jar 文件,就能直接在 JDK 上运行。在使用这两个函数转换之前,需要先创建好对象的类型以及其成员才能成功的将 JSON 字符串转换为相对应的对象。

类里面只要有 get 和 set 方法,Gson 完全可以将复杂类型的 JSON 到 Bean 或 Bean 到 JSON 的转换,是 JSON 解析的神器。Gson 在功能上面无可挑剔,但性能比 FastJson 有所差距。

4)阿里巴巴的FastJson

FastJson 是用 Java 语言编写的高性能 JSON 处理器,由阿里巴巴公司开发。

FastJson 不需要依赖其它的 jar 文件,就能直接在 JDK 上运行。

FastJson 在复杂类型的 Bean 转换 JSON 上会出现一些问题,可能会出现引用的类型,导致 JSON 转换出错,需要制定引用。

FastJson 采用独创的算法,将 parse 的速度提升到极致,超过所有 JSON 库。

综上 4 种 JSON 技术的比较,在项目选型的时候可以使用 Google 的 Gson 和阿里巴巴的 FastJson 两种并行使用,如果只是功能要求,没有性能要求,可以使用Google 的 Gson。如果有性能上面的要求可以使用 Gson 将 Bean 转换 JSON 确保数据的正确,使用 FastJson 将 JSON 转换 Bean。

示例

本节示例基于阿里巴巴提供的 FastJson。下面结合具体需求演示 Spring MVC 如何处理 JSON 格式数据。(本节代码基于《》一节的 springmvcDemo2 实现)

1. 导入jar文件

导入所需 jar 包 fastjson-1.2.62.jar,下载地址:https://github.com/alibaba/fastjson/releases。

Maven 项目在 pom.xml 文件中添加以下依赖。

<!-- fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>

2. 配置Spring MVC核心配置文件

在 springmvc-servlet.xml 中添加以下代码。

<mvc:annotation-driven>
<!--配置@ResponseBody由fastjson解析 -->
    <mvc:message-converters>
        <bean
            class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="defaultCharset" value="UTF-8" />
        </bean>
        <bean
            class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4" />
    </mvc:message-converters>
</mvc:annotation-driven>
<mvc:default-servlet-handler />
<bean id="fastJsonpResponseBodyAdvice"
    class="com.alibaba.fastjson.support.spring.FastJsonpResponseBodyAdvice">
    <constructor-arg>
        <list>
            <value>callback</value>
            <value>jsonp</value>
        </list>
    </constructor-arg>
</bean>
<!-- annotation-driven用于简化开发的配置,注解DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter -->
<!-- 使用resources过滤掉不需要dispatcherservlet的资源(即静态资源,例如css、js、html、images)。
    在使用resources时必须使用annotation-driven,否则resources元素会阻止任意控制器被调用 -->
<!-- 允许js目录下的所有文件可见 -->
<mvc:resources location="/" mapping="/**" />

3. 创建POJO类

在 net.biancheng.pojo 包下创建 User 类,代码如下。

package net.biancheng.po;
public class User {
    private String name;
    private String password;
    private Integer age;
    
    /**省略setter和getter方法*/
}

4. 创建JSP页面

创建 index.jsp 页面测试 JSON 数据交互,代码如下。

<%@ 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>测试JSON交互</title>
<script type="text/javaScript"
    src="${pageContext.request.contextPath }/js/jquery-3.2.1.min.js"></script>
</head>
<body>
    <form action="">
        用户名:<input type="text" name="name" id="name" />
        <br>
        密码:<input type="password" name="password" id="password" />
        <br>
        年龄:<input type="text" name="age" id="age">
        <br>
        <input type="button" value="测试" onclick="testJson()" />
    </form>
</body>
<script type="text/javaScript">
    function testJson() {
        var name = $("#name").val();
        var password = $("#password").val();
        var age = $("#age").val();
        $.ajax({
            //请求路径
            url : "${pageContext.request.contextPath}/testJson",
            //请求类型
            type : "post",
            //data表示发送的数据
            data : JSON.stringify({
                name : name,
                password : password,
                age : age
            }), //定义发送请求的数据格式为JSON字符串
            contentType : "application/json;charset=utf-8",
            //定义回调响应的数据格式为JSON字符串,该属性可以省略
            dataType : "json",
            //成功响应的结果
            success : function(data) {
                if (data != null) {
                    alert("输入的用户名:" + data.name + ",密码:" + data.password
                            + ", 年龄:" + data.age);
                }
            }
        });
    }
</script>
</body>
</html>

由于在 index.jsp 中使用的是 JQuery 的 AJAX 进行的 JSON 的数据提交和响应,所以还需要引入 jquery.js 文件。这里我们引入的是 webapp 目录下的 js 文件夹中的 jquery-3.2.1.min.js。

5. 创建控制器

在 UserController 中添加以下代码。

/**
* 接收页面请求的JSON参数,并返回JSON格式的结果
*/
@RequestMapping("/testJson")
@ResponseBody
public User testJson(@RequestBody User user) {
    // 打印接收的 JSON数据
    System.out.println("name=" + user.getName() + ",password=" + user.getPassword() + ",age=" + user.getAge());
    // 返回JSON格式的响应
    return user;
}

在上述控制器类中编写了接收和响应 JSON 格式数据的 testJson 方法,方法中的 @RequestBody 注解用于将前端请求体中的 JSON 格式数据绑定到形参 user 上,@ResponseBody 注解用于直接返回 Person 对象(当返回 POJO 对象时默认转换为 JSON 格式数据进行响应)。

6. 测试JSON数据交互

访问地址:http://localhost:8080/springmvcDemo2/index.jsp,运行结果如下。

index.jsp页面
index.jsp 页面

输入信息后点击”测试“按钮,将弹出显示输入信息的对话框,如下图所示。

img

Eclipse 控制台输出信息如下。

name=biancheng.net,password=123456,age=12

从运行结果可以看出,编写的代码可以将 JSON 格式的请求数据转换为方法中的 Java 对象,也可以将 Java 对象转换为 JSON 格式的响应数据。

Spring MVC数据校验

一般情况下,用户的输入是随意的,为了保证数据的合法性,数据验证是所有 Web 应用必须处理的问题。

Spring MVC 有以下两种方法可以验证输入:

  • 利用 Spring 自带的验证框架
  • 利用 JSR 303 实现

数据验证分为客户端验证和服务器端验证,客户端验证主要是过滤正常用户的误操作,通过 JavaScript 代码完成。服务器端验证是整个应用阻止非法数据的最后防线,通过在应用中编程实现。

本节使用 JSR 303 实现服务器端的数据验证。

JSR 303 是 Java 为 Bean 数据合法性校验所提供的标准框架。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证。可以通过 https://jcp.org/en/jsr/detail?id=303 查看详细内容并下载 JSR 303 Bean Validation。

JSR 303 不需要编写验证器,它定义了一套可标注在成员变量、属性方法上的校验注解,如下表所示。

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

Spring MVC 支持 JSR 303 标准的校验框架,Spring 的 DataBinder 在进行数据绑定时,可同时调用校验框架来完成数据校验工作,非常简单方便。在 Spring MVC 中,可以直接通过注解驱动的方式来进行数据校验。

Spring 本身没有提供 JSR 303 的实现,Hibernate Validator 实现了 JSR 303,所以必须在项目中加入来自 Hibernate Validator 库的 jar 文件,下载地址为 http://hibernate.org/validator/。本节使用版本为 hibernate-validator-5.1.0.Final-dist.zip,复制其中的 3 个 jar 文件即可,Spring 将会自动加载并装配。

  • hibernate-validator-5.1.0.Final.jar
  • jboss-logging-3.1.0.CR2.jar
  • validation-api-1.0.0.GA.jar

示例

本节示例基于《@Controller和@RequestMapping注解》一节中的 springmvcDemo2 实现。

1. 导入依赖

pom.xml 文件中添加以下代码。

<!-- 数据校验 -->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>
<dependency>
    <groupId>org.jboss.logging</groupId>
    <artifactId>jboss-logging</artifactId>
    <version>3.1.0.CR2</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.1.0.Final</version>
</dependency>

2. 创建实体类

创建 User 实体类,代码如下。

package net.biancheng.po;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
public class User {
    @NotNull(message = "用户id不能为空")
    private Integer id;
    @NotNull
    @Length(min = 2, max = 8, message = "用户名不能少于2位大于8位")
    private String name;
    @Email(regexp = "[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]", message = "邮箱格式不正确")
    private String email;
    /** 省略setter和getter方法*/
}

3. 创建JSP页面

创建 addUser.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>添加用户</title>
</head>
<body>
    <form action="${pageContext.request.contextPath}/validate" method="post">
        用户id:<input type="text" name="id" />
        <br>
        用户名:<input type="text" name="name" />
        <br>
        邮箱:<input type="text" name="email" />
        <br>
        <input type="submit" value="提交" />
    </form>
</body>
</html>

4. 创建控制器

创建 UserController 控制器类,代码如下。

package net.biancheng.controller;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import net.biancheng.po.User;
@Controller
public class UserController {
    @RequestMapping("/validate")
    public String validate(@Valid User user, BindingResult result) {
        // 如果有异常信息
        if (result.hasErrors()) {
            // 获取异常信息对象
            List<ObjectError> errors = result.getAllErrors();
            // 将异常信息输出
            for (ObjectError error : errors) {
                System.out.println(error.getDefaultMessage());
            }
        }
        return "index";
    }
    @RequestMapping(value = "/addUser")
    public String add() {
        return "addUser";
    }
}

5. 运行测试

访问地址:http://localhost:8080/springmvcDemo2/addUser,运行结果如下。

img

Eclipse 控制台输出结果如下。

邮箱格式不正确
用户id不能为空
用户名不能少于2位大于8位

Spring MVC REST风格

REST(Representational State Transfer)即表述性转移,是目前最流行的一种软件架构风格。它结构清晰、易于理解、有较好的扩展性。

REST 概念较为复杂,我们不过多解释,大家简单了解 Spring MVC 的 REST 风格的简单使用即可。

Spring REST 风格可以简单理解为:使用 URL 表示资源时,每个资源都用一个独一无二的 URL 来表示,并使用 HTTP 方法表示操作,即准确描述服务器对资源的处理动作(GET、POST、PUT、DELETE),实现资源的增删改查。

  • GET:表示获取资源
  • POST:表示新建资源
  • PUT:表示更新资源
  • DELETE:表示删除资源

下面举例说明 REST 风格的 URL 与传统 URL 的区别。

/userview.html?id=12 VS /user/view/12
/userdelete.html?id=12 VS /user/delete/12
/usermodify.html?id=12 VS /user/modify/12

我们发现 REST 风格的 URL 中最明显的就是参数不再使用“?”传递。这种风格的 URL 可读性更好,使得项目架构清晰,最关键的是 Spring MVC 也提供对这种风格的支持。

但是也有弊端,对于国内项目,URL 参数有时会传递中文,而中文乱码是一个令人头疼的问题,所以我们应该根据实际情况进行灵活处理。很多网站都是传统 URL 风格与 REST 风格混搭使用。

示例

由于 HTTP 不支持 PUT 和 DELETE 请求,所以需要将 DELETE 和 PUT 请求转换成 POST 请求,在 web.xml 中配置过滤器 HiddenHttpMethodFilter。

<!-- HiddenHttpMethodFilter过滤器可以将POST请求转化为put请求和delete请求! -->
<filter>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

新建 rest.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>REST风格</title>
</head>
<body>
    <h4>发送GET请求</h4>
    <a href=" user/1">GET</a>
    <h4>发送POST请求</h4>
    <form action="user/1" method="post">
        <input type="submit" value="POST" />
    </form>
    <!-- 发送PUT和DELETE请求时,需要添加一个隐藏域 -->
    <h4>发送PUT请求</h4>
    <form action=" user/1" method="post">
        <input type="hidden" name="_method" value="PUT" /> <input
            type="submit" value="PUT" />
    </form>
    <h4>发送DELETE请求</h4>
    <input type="hidden" name="_method" value="DELETE" />
    <form action=" user/1" method="post">
        <input type="hidden" name="_method" value="DELETE" /> <input
            type="submit" value="DELETE" />
    </form>
</body>
</html>

下面通过 @RequestMapping 映射请求中的 method 参数实现四种请求方式的调用,UserController 代码如下。

package net.biancheng.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class UserController {
    @RequestMapping("/torest")
    public String torest() {
        return "rest";
    }
    @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
    public String hello(@PathVariable Integer id) {
        System.out.println("test rest get:" + id);
        return "success";
    }
    @RequestMapping(value = "/user/{id}", method = RequestMethod.POST)
    public String hello() {
        System.out.println("test POST:");
        return "success";
    }
    @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
    public String helloDelete(@PathVariable Integer id) {
        System.out.println("test rest delete:" + id);
        return "success";
    }
    @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
    public String helloPUt(@PathVariable Integer id) {
        System.out.println("test rest put:" + id);
        return "success";
    }
}

访问地址:http://localhost:8080/springmvcDemo2/torest,运行结果如下。

img

依次点击请求按钮,控制器输出结果如下。

test rest get:1
test POST:
test rest put:1
test rest delete:1

Spring MVC文件上传

Spring MVC文件下载

顾名思义,文件下载就是将服务器中的文件下载到本地,下面主要介绍 Spring MVC 文件下载的实现方法和实现过程。

文件下载的实现方法

文件下载有以下两种实现方法:

  • 通过超链接实现下载:实现简单,但暴露了下载文件的真实位置,并且只能下载 Web 应用程序所在目录下的文件,WEB-INF 目录除外。
  • 利用程序编码实现下载:增强安全访问控制,可以下载除 Web 应用程序所在目录以外的文件,也可以将文件保存到数据库中。

利用程序编码实现下载需要设置以下两个报头:

  1. Web 服务器需要告诉浏览器其所输出内容的类型不是普通文本文件或 HTML 文件,而是一个要保存到本地的下载文件,这需要设置 Content-Type 的值为 application/x-msdownload。
  2. Web 服务器希望浏览器不直接处理相应的实体内容,而是由用户选择将相应的实体内容保存到一个文件中,这需要设置 Content-Disposition 报头。

该报头指定了接收程序处理数据内容的方式,在 HTTP 应用中只有 attachment 是标准方式,attachment 表示要求用户干预。在 attachment 后面还可以指定 filename 参数,该参数是服务器建议浏览器将实体内容保存到文件中的文件名称。

设置报头的示例如下:

response.setHeader(“Content-Type”, “application/x-msdownload”);
response.setHeader(“Content-Disposition”, “attachment;filename=”+filename);

程序编码文件下载可分为两个步骤:

  1. 在客户端使用一个文件下载超链接,链接指向后台下载文件的方法以及文件名。
  2. 在控制器类中,提供文件下载方法进行下载。

示例

下面继续通过 springMVCDemo2 应用讲述利用程序实现下载的过程,要求从《Spring MVC文件上传》上传文件的目录(workspace.metadata.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\springMVCDemo2\uploadfiles)中下载文件,具体开发步骤如下。

1. 编写控制器类

首先编写控制器类 FileDownController,在该类中有 3 个方法,即 show、down 和 toUTF8String。其中,show 方法获取被下载的文件名称;down 方法执行下载功能;toUTF8String 方法是下载保存时中文文件名的字符编码转换方法。

FileDownController 类的代码如下:

package controller;
import java.io.File;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class FileDownController {
    // 得到一个用来记录日志的对象,在打印时标记打印的是哪个类的信息
    private static final Log logger = LogFactory
            .getLog(FileDownController.class);
    /**
     * 显示要下载的文件
     */
    @RequestMapping("showDownFiles")
    public String show(HttpServletRequest request, Model model) {
        // 从 workspace\.metadata\.plugins\org.eclipse.wst.server.core\
        // tmp0\wtpwebapps\springMVCDemo11\下载
        String realpath = request.getServletContext()
                .getRealPath("uploadfiles");
        File dir = new File(realpath);
        File files[] = dir.listFiles();
        // 获取该目录下的所有文件名
        ArrayList<String> fileName = new ArrayList<String>();
        for (int i = 0; i < files.length; i++) {
            fileName.add(files[i].getName());
        }
        model.addAttribute("files", fileName);
        return "showDownFiles";
    }
    /**
     * 执行下载
     */
    @RequestMapping("down")
    public String down(@RequestParam String filename,
            HttpServletRequest request, HttpServletResponse response) {
        String aFilePath = null; // 要下载的文件路径
        FileInputStream in = null; // 输入流
        ServletOutputStream out = null; // 输出流
        try {
            // 从workspace\.metadata\.plugins\org.eclipse.wst.server.core\
            // tmp0\wtpwebapps下载
            aFilePath = request.getServletContext().getRealPath("uploadfiles");
            // 设置下载文件使用的报头
            response.setHeader("Content-Type", "application/x-msdownload");
            response.setHeader("Content-Disposition", "attachment; filename="
                    + toUTF8String(filename));
            // 读入文件
            in = new FileInputStream(aFilePath + "\\" + filename);
            // 得到响应对象的输出流,用于向客户端输出二进制数据
            out = response.getOutputStream();
            out.flush();
            int aRead = 0;
            byte b[] = new byte[1024];
            while ((aRead = in.read(b)) != -1 & in != null) {
                out.write(b, 0, aRead);
            }
            out.flush();


# 最后

金三银四马上就到了,希望大家能好好学习一下这些技术点

学习视频:

![](https://img-blog.csdnimg.cn/img_convert/07fc3ed1f3e15636e49f470f9d279fc6.webp?x-oss-process=image/format,png)

大厂面试真题:

![](https://img-blog.csdnimg.cn/img_convert/e09c91f93785484604e77939a6bd9696.webp?x-oss-process=image/format,png)

> **本文已被[CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)收录**

**[需要这份系统化的资料的朋友,可以点击这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**

re\
        // tmp0\wtpwebapps\springMVCDemo11\下载
        String realpath = request.getServletContext()
                .getRealPath("uploadfiles");
        File dir = new File(realpath);
        File files[] = dir.listFiles();
        // 获取该目录下的所有文件名
        ArrayList<String> fileName = new ArrayList<String>();
        for (int i = 0; i < files.length; i++) {
            fileName.add(files[i].getName());
        }
        model.addAttribute("files", fileName);
        return "showDownFiles";
    }
    /**
     * 执行下载
     */
    @RequestMapping("down")
    public String down(@RequestParam String filename,
            HttpServletRequest request, HttpServletResponse response) {
        String aFilePath = null; // 要下载的文件路径
        FileInputStream in = null; // 输入流
        ServletOutputStream out = null; // 输出流
        try {
            // 从workspace\.metadata\.plugins\org.eclipse.wst.server.core\
            // tmp0\wtpwebapps下载
            aFilePath = request.getServletContext().getRealPath("uploadfiles");
            // 设置下载文件使用的报头
            response.setHeader("Content-Type", "application/x-msdownload");
            response.setHeader("Content-Disposition", "attachment; filename="
                    + toUTF8String(filename));
            // 读入文件
            in = new FileInputStream(aFilePath + "\\" + filename);
            // 得到响应对象的输出流,用于向客户端输出二进制数据
            out = response.getOutputStream();
            out.flush();
            int aRead = 0;
            byte b[] = new byte[1024];
            while ((aRead = in.read(b)) != -1 & in != null) {
                out.write(b, 0, aRead);
            }
            out.flush();


# 最后

金三银四马上就到了,希望大家能好好学习一下这些技术点

学习视频:

[外链图片转存中...(img-xQwKiyz4-1715478519491)]

大厂面试真题:

[外链图片转存中...(img-PnNKrCbp-1715478519492)]

> **本文已被[CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)收录**

**[需要这份系统化的资料的朋友,可以点击这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值