只要能够运行一个Web应用,就能运行更加复杂的应用,只需要将更多的组件和业务逻辑组装起来。Web开发在配置好了最基础的项目之后,接下来的传统艺能无非就是增删改查和身份认证,之后就是无限的可能性了。

增删改查的最基础的一步,就是读取客户端的请求中附带的数据。

从请求中获取属性和值-${param}

先来做一个最简单的表单提交一个数据的应用,表单采用GET请求提交数据,意味着我们要通过GET请求取得附带的参数。

先来设计一下架构,由于有过之前的开发经验,我们想到,用一个url返回表单页面,由于表单是GET请求,所以让表单发送到另外一个url,取得表单数据后简单展示在一个新的页面上。

于是需要两个url对应的控制方法,一个展示表单的页面和一个结果页面:创建一个新的控制器叫做BasicFormController,其中两个方法用于控制两个url,然后一个first-form.jsp,一个result.jsp。

先编写控制器:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.springframework.web.bind.annotation.RequestMethod.GET;

@Controller
public class BasicFormController {

    @RequestMapping(value = "/form", method = GET)
    public String showForm() {
        return "first-form";
    }

    @RequestMapping(value = "/processForm", method = GET)
    public String processForm() {
        return "result";
    }
}

这里对@RequestMapping(value = "/form", method = GET)用法做了一点升级,value表示匹配的url,method表示方法,除了GET之外还可以匹配POST。

用一个控制器,其中的两个方法分别展示表单页面和处理表单数据并返回结果页面。来编写first-form.jsp:

<%@ page contentType="text/html;charset=UTF-8" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
    <title>FirstSpringMVC</title>
</head>
<body>
<form action="processForm" method="get">
    Student name: <input type="text" name="studentName" placeholder="What's your name?">
    <input type="submit">
</form>
</body>
</html>

这个jsp文件很简单,就一个表单,提交请求到/processForm路径,使用GET方法。再来编写result.jsp:

<%@ page contentType="text/html;charset=UTF-8" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
    <title>FirstSpringMVC</title>
</head>
<body>
<p>Dear student:</p>
<p>Your name is <span style="color:darkmagenta">${param.studentName}</span></p>
</body>
</html>

这里唯一一点要注意的是,GET请求进来的参数名称是studentName,这里用Spring表达式${param.studentName}直接从参数上下文中取得了对应的名称显示在结果中。

还可以尝试增加表单的内容,同样使用Spring表达式来取值。这里如果将表单的请求方式改成POST,将控制器的processForm方法的@RequestMapping注解的参数修改为POST,结果也可以正常显示。

这里实验了一下获得Checkbox的数据,发现${param}只能获得第一个数据,可见还会有其他的办法获取Checkbox类的一组数据的方法。

使用Spring Model对象传递数据-${model的属性名}

Spring MVC内部有一个Model对象,是一个Web应用内数据的容器。在控制器中,可以向Model对象内放入任何数据,在处理完请求之后,View页面可以从Model对象中取得控制器放入的数据,这样就实现了控制与视图之间的数据传递。

Spring MVC的控制器方法中,传入的参数是任意需要的参数,比如需要HTTP请求对象,则可以传入HTTPServletRequest,需要Model对象,则可以传入Model类型的参数。

这里对上面的processForm方法进行一下修改,使用Model对象传递全部大写的学生名称,在result.jsp中增加一行来展示这个经过Model传递来的数据。

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.web.bind.annotation.RequestMethod.GET;

@Controller
public class BasicFormController {

    @RequestMapping(value = "/form", method = GET)
    public String showForm() {
        return "first-form";
    }

    @RequestMapping(value = "/processForm", method = GET)
    public String processForm(HttpServletRequest request, Model model) {
        String studentName = request.getParameter("studentName").toUpperCase();
        model.addAttribute("message", studentName);
        return "result";
    }
}

这里如果需要使用HttpServletRequest类,需要导入Tomcat的包,不过会有提示说jar未被载入,但是可以正常使用request对象和编译通过,暂时不知道为什么。可能是Tomcat本身的包有点老,应该还是使用Tomcat本身附带的包比较好一些。

这个一个主要的变化就是,从request对象中获取到了参数studentName的值,然后将其大写之后,写入到Model对象中,当然Model类也需要从Spring中导入。

在修改好控制器方法后,在result.jsp中新增一行用于从Model中获取数据。

<body>
<p>Dear student:</p>
<p>Your name is <span style="color:darkmagenta">${param.studentName}</span></p>
<p>Your name from model is <span style="color:darkblue">${message}</span></p>
<p><a href="/form">返回表单页</a></p>
</body>

可以看到,Model就相当于一个域对象。我看了Spring的一些介绍材料上说,一般在Spring框架中,极少像JavaEE中直接使用Request和Response对象。那么这里的Model对象就起到了在整个Web应用处理响应的过程中传递数据的功能。

既然是域对象,Model中可以放入任何东西,尝试着放了一些Java的集合和Map对象,都可以正常显示出对象的toString()方法的结果。

直接绑定request中的参数值,无需使用request对象

在上一个例子里提到过,一般Spring框架较少使用Request对象,那么如何在不通过Request对象的情况下获取参数呢。其实既然能用Spring表达式在jsp中获取,说明框架肯定已经帮我们截取到了Request中的参数。

继续修改控制器如下:

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 static org.springframework.web.bind.annotation.RequestMethod.GET;

@Controller
public class BasicFormController {

    @RequestMapping(value = "/form", method = GET)
    public String showForm() {
        return "first-form";
    }

    @RequestMapping(value = "/processForm", method = GET)
    public String processForm(@RequestParam("studentName") String studentName, Model model) {
        studentName = studentName.toUpperCase();
        model.addAttribute("message", studentName);
        return "result";
    }
}

这里有点像之前在学习依赖注入的时候直接用注解来标识参数,也用了@RequestParam带上参数的名称来修饰控制器方法的参数,这样就将字符串类型的studentName变量直接绑定了请求中的属性。

而且比较两个控制器方法,可以发现这么操作无需再导入import javax.servlet.http.HttpServletRequest;,这是一种非常Spring的做法,推荐使用。从中也可以看到Spring控制器方法灵活使用参数的强大功能。

控制器层面的@RequestMapping注解

目前为止,只把@RequestMapping使用在了控制器方法上,实际上该注解也可以使用在控制器上,也可以同时使用在控制器和控制方法上。

同时使用的话,控制器的注解先发生作用,控制方法的注解会对控制器的注解进行补充,尤其是在路径方面,相当于控制器注解是一个目录,而控制方法对应的是子目录。

来修改一下我们的控制器,将路径变得更加清晰一些:

@Controller
@RequestMapping(value = "/form")
public class BasicFormController {

    @RequestMapping(value = "/show", method = GET)
    public String showForm() {
        return "first-form";
    }

    //原来的processForm方法
//    @RequestMapping(value = "/processForm", method = POST)
//    public String processForm() {
//        return "result";
//    }

    @RequestMapping(value = "/result", method = GET)
    public String processForm(@RequestParam("studentName") String studentName, Model model) {
        studentName = studentName.toUpperCase();
        model.addAttribute("message", studentName);
        return "result";
    }
}

这样配置完之后的结果是,如果要展示表单,访问的相对路径是/form/show,而表单被提交的路径是/form/result,修改一下两个jsp文件中的路径:

<%--first-form.jsp--%>
<form action="/form/result" method="get">
    Student name: <input type="text" name="studentName" placeholder="What's your name?">

    <input type="submit">
</form>

<%--result.jsp--%>
<p><a href="/form/show">返回表单页</a></p>

重新部署之后,访问表单的地址就变成了http://localhost:8080/form/show,输入之后发现结果页的地址变成了http://localhost:8080/form/result?studentName=testname